Pythonで簡単ゲームを作成(PyGame)

python Python

Python + PyGameで簡単なシューティングゲームを作成しました。
IDEはVScodeを利用しています。

シューティングゲーム

横から飛んでくる円盤を撃つゲームです。
高い位置の円盤の方が得点が高くなっています。
マウスで左右移動、左クリックで弾を発射します。
円盤を10枚撃ち損じるとゲームオーバーです。

 

 

import pygame
from pygame.locals import *
import random
import sys

# 画面サイズ
WD_RECT = Rect(0, 0, 640, 400)
# 弾状態
BL_STOP = 0
BL_MOVE = 1
# 弾速度
BL_SPEED = 10

# 銃のクラス
class Gun(pygame.sprite.Sprite):
    # コンストラクタ
    def __init__(self, filename):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.image = pygame.image.load(filename).convert()
        self.rect = self.image.get_rect()
        self.rect.bottom = WD_RECT.bottom - 20
    
    def update(self):
        self.rect.centerx = pygame.mouse.get_pos()[0]   # マウスx座標を銃のx座標に
        self.rect.clamp_ip(WD_RECT)                     # 画面内のみにクランプ

# 弾のクラス
class Bullet(pygame.sprite.Sprite):
    # コンストラクタ
    def __init__(self, filename, gun):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.image = pygame.image.load(filename).convert()
        self.rect = self.image.get_rect()
        self.gun = gun
        self.dx = self.dy = 0
        self.state = BL_STOP

    def update(self):
        if self.state == BL_STOP:
            # 初期位置を銃の上にする
            self.rect.centerx = self.gun.rect.centerx
            self.rect.bottom = self.gun.rect.top

            #左クリックで発射
            if pygame.mouse.get_pressed()[0] == 1:
                self.dx = 0
                self.dy = BL_SPEED
                self.state = BL_MOVE
        elif self.state == BL_MOVE:
            # 弾飛中
            self.rect.centery -= self.dy
            
            #画面外に出た場合
            if self.rect.top < WD_RECT.top:
                self.state = BL_STOP
            if self.rect.left < WD_RECT.left:
                self.state = BL_STOP
            if self.rect.right > WD_RECT.right:
                self.state = BL_STOP
       

# 円盤のクラス
class Disk(pygame.sprite.Sprite):
    # コンストラクタ
    def __init__(self, filename):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.image = pygame.image.load(filename).convert()
        self.rect = self.image.get_rect()
        # 方向 500未満:左から右へ、500以上:右→左へ
        # 0,1をランダムするよりバラけるかなと。
        self.direction = random.randint(1,1000)
        if self.direction < 500:
            self.rect.centerx = WD_RECT.left
        else:
            self.rect.centerx = WD_RECT.right
        self.rect.centery = random.randint(50, 200)
        self.dx = 2
        self.remain = 10    # 残数
     
    def update(self):
        if self.direction < 500 :
            self.rect.centerx += self.dx
        else :
            self.rect.centerx -= self.dx
        #画面外に出た場合
        if self.rect.right < WD_RECT.left:
            #self.rect.centerx = WD_RECT.right
            # 残数を減らす
            self.remain -= 1
            self.collided()
        if self.rect.left > WD_RECT.right:
            #self.rect.centerx = WD_RECT.left
            # 残数を減らす
            self.remain -= 1
            self.collided()
    
    def collided(self):
        self.rect.centery = random.randint(50, 200)
        self.direction = random.randint(1,1000)
        if self.direction < 500:
            self.rect.centerx = WD_RECT.left
        else:
            self.rect.centerx = WD_RECT.right
        
        # 円盤の速度をランダムで変える
        self.dx = random.randint(2,5)
 
        

# 点数表示クラス
class Score():
    def __init__(self):
        self.sysfont = pygame.font.SysFont(None, 20)
        self.score = 0
    def draw(self, screen):
        img = self.sysfont.render("SCORE:"+str(self.score), True, (255,255,255))
        screen.blit(img, (10, 10))
    def add_score(self, x):
        self.score += x
    
# 当てた際の点数をヒット位置に表示するクラス
class Hit_score():
    def __init__(self):
        self.sysfont = pygame.font.SysFont(None, 20)
        self.inc = 0
        self.x = 0
        self.y = 0
        self.cont = 0
    # 当たった時にこのメソッドを読んで、必要値をセット
    def hit(self, inc, x, y):
        self.inc = inc
        self.x = x
        self.y = y
        self.cont = 1
    # 表示処理
    def draw(self, screen):
        # 20フレームぐらい表示すれば見えると思う
        if self.cont > 0 and self.cont < 20:
            if self.inc >= 1000 :
                # 1000点以上は青字にしてみた
                img = self.sysfont.render(str(self.inc), True, (128,128,255))
            else :
                img = self.sysfont.render(str(self.inc), True, (255,255,255))
            screen.blit(img, (self.x, self.y))
            self.cont += 1
            # y座標を減らして上方向に上がりながら表示させる
            self.y -= 1

# 円盤を打ち損じた数を表示するクラス
class Miss():
    def __init__(self):
        self.sysfont = pygame.font.SysFont(None, 20)

    def draw(self, screen, miss):
        if miss < 7 :
            img = self.sysfont.render("Miss:"+str(miss)+"/10", True, (255,255,255))
        else :
            # 残り3以下は赤色表示
            img = self.sysfont.render("Miss:"+str(miss)+"/10", True, (255,128,128))
        screen.blit(img, (WD_RECT.right - 100, 10))
 
class Gameover():
    def __init__(self):
        self.sysfont = pygame.font.SysFont(None, 50)

    def draw(self, screen, score):
        wx = (WD_RECT.right / 2) - 100
        wy = (WD_RECT.bottom / 2) - 50
        self.sysfont = pygame.font.SysFont(None, 50)
        img = self.sysfont.render("Game Over!!", True, (128,128,255))
        screen.blit(img, (wx, wy))

        img = self.sysfont.render("Score:"+str(score), True, (128,128,255))
        wy += 35
        screen.blit(img, (wx, wy))

        self.sysfont = pygame.font.SysFont(None, 20)
        img = self.sysfont.render("Push [ESC]key will quit.", True, (128,255,128))
        wy += 35
        screen.blit(img, (wx+10, wy))
        img = self.sysfont.render("Push [Enter]key will restart.", True, (128,255,128))
        wy += 15
        screen.blit(img, (wx+10, wy))

def main():
    pygame.init()
    screen = pygame.display.set_mode(WD_RECT.size)
    pygame.display.set_caption("game01")

    # 描画用スプライトグループ
    gr_draw = pygame.sprite.RenderUpdates()
    # 衝突判定用スプライトグループ
    gr_collision = pygame.sprite.Group()

    Gun.containers = gr_draw
    Bullet.containers = gr_draw
    Disk.containers = gr_draw, gr_collision

    # インスタンス作成
    gun = Gun("gun.png")
    bullet = Bullet("bullet.png", gun)
    disk = Disk("disk.png")
    score = Score()
    hit_score = Hit_score()
    miss = Miss()
    gameov = Gameover()

    clock = pygame.time.Clock()

    while True:
        # フレームレート60fps
        clock.tick(60)
        screen.fill((0, 20, 0))

        # 円盤を10枚打ち損じたら終了
        if disk.remain <= 0:
            # ゲームオーバー表示
            gameov.draw(screen, score.score)
        else :
            # 全スプライトグループ更新
            gr_draw.update()
            gr_collision.update()
            
            # 衝突判定(とりあえずmain直書き、気が向いたらクラス化するかも)
            # 弾に当たった円盤のリストを取り出し、その円盤はスプライトグループから削除(表示消える)
            disks_collided = pygame.sprite.spritecollide(bullet, gr_collision, True)
            if disks_collided:
                for bkdisk in disks_collided:
                    # スコア増加 上の方が点数高い
                    # ランダムの最下座標200dot + 10からマイナスして10倍する
                    inc_score = (210 - bkdisk.rect.centery) * 10
                    score.add_score(inc_score)

                    # 当てた時のスコアを命中位置にポワっと表示のための情報セット
                    hit_score.hit(inc_score, bkdisk.rect.centerx, bkdisk.rect.centery)

                    # 弾に当たった円盤位置初期化
                    bkdisk.collided()
                    # 弾に当たった円盤をスプライトグループに再登録
                    bkdisk.add(gr_collision)


        # 全スプライトグループ描画
        gr_draw.draw(screen)
        gr_collision.draw(screen)

        # スコア描画
        score.draw(screen)
        hit_score.draw(screen)
        miss.draw(screen, 10 - disk.remain)

        # 画面更新
        pygame.display.update()
          

        #Event loop
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and event.key == K_RETURN:
                if disk.remain <= 0:
                    # 残数が0の時にEnterキーでリスタート
                    disk.remain = 10
                    score.score = 0
        
if __name__ == "__main__":
    main()

悩んだ点

  • 弾に当たった円盤の表示の消し方:円盤をスプライトグループから削除する。
  • そしたら円盤が表示されなくなったぞ!:円盤をスプライトグループに再登録する。

使用しているpng画像は適当にペイントで描いたものです。
使用png画像





bullet.png:弾。幅8 x 高さ8ピクセル。
disk.png:円盤。幅25 x 高さ8ピクセル。
gun.png:銃。幅17 x 高さ39ピクセル。

凝った画像にすると雰囲気が変わるかもしれません。

タイトルとURLをコピーしました