LoginSignup
3
2

More than 3 years have passed since last update.

pygameで風船割りゲーム

Posted at

はじめに

前回の投稿では、tkinterを使ったターン制の三目並べを作成したので、今回はpygameを使った常に画面が更新されるものを作ってみました。
タイトルで風船割りゲームと書きましたがそこまでのゲーム性はありません。笑
ただ、今後ゲームを作る際に使えそう?な処理は学べたかなぁ。。。と思います。
例によって、欲しい機能を寄せ集めたキメラティックコードなので読みやすさは今後の課題ですね。

今回の完成品
balloon_game.gif

一番最後に今回のコード全体像を載せてあります。

表示画面作成

まずは基盤となる画面作成からですね。

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Game")

なにはともあれpygameをimport。
pygame.init()で、pygameのモジュールを初期化してあげます。開幕に書く場合は、お決まりの呪文レベルの認識でいいかもしれません。大量のモジュールを使用する場合は、使ってないモジュールを初期化してあげるといいとかなんとか。そのうち処理の途中で書く日が来るかも?

pygame.display.set_modeが画面サイズで横幅、縦幅。
set_captionはタイトルバーに表示される名前です。

画面更新処理

import time

FPSCLOCK = pygame.time.Clock()
FPS = 15

class CanonGame():
    def __init__(self):

        while True:
            pygame.display.update()
            FPSCLOCK.tick(FPS)

def main():
    CanonGame()

if __name__ == '__main__':
    main()

主役。
pygame.display.update()で画面の描画を反映した後に、pygame.time.Clock.tick(フレーム数)で1秒間に(フレーム数)を超えない速度で処理が実行されます。フレーム数を上げれば、滑らかな動きを実装することができる反面、処理が重くなってしまいます。
それと、基本的に更新し続けるものはwhile True:の範囲中に入れてあげてください。

キーイベント取得

from pygame.locals import *

pygame.key.set_repeat(5, 5)

class CanonGame():
        while True:
            for event in pygame.event.get():
                if event.type == QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == KEYDOWN:
                    if event.key == K_SPACE:
                        self.bullet_shot = True
                    elif event.key == K_UP and self.cannon_radian < 89:
                        self.cannon_radian += 1
                    elif event.key == K_DOWN and self.cannon_radian > 0:
                        self.cannon_radian -= 1

ゲームとなればやはり操作したいですよね?
event.get()で行われた操作の種類を取得します。
event.typeがQUIT(画面右上の×ボタン)ならすべてを終了させます。
KEYDOWN(キーボード押下)ならその後にevent.keyを取得して押されたキーごとに処理を割り振ります。
今回は、スペースキーで球を発射。上下キーで向き調節にしました。
key.set_repeatを設定することによりキーを押しっぱなしでも反応してくれるようになります。

画像表示、回転


class CanonGame():
    def __init__(self):
        bullet_image = pygame.image.load("bullet.png")
        self.cannon_image = pygame.image.load("cannon.png")

            screen.fill((255, 255, 255))
            screen.blit(bullet_image, (self.bullet_x, self.bullet_y))

            rotate_cannon = pygame.transform.rotate(self.cannon_image, self.cannon_radian - 45)
            cannon_rect = rotate_cannon.get_rect()
            cannon_rect.center = (84, 536)
            screen.blit(rotate_cannon, cannon_rect)

pygame.image.load(”画像名”)で画像を読み込みます。画像の保存場所は実行ファイルを保存している場所と同じ場所で。
screen.fill(Red,Green,Blue)は背景色です。今回は背景画像を用意していないので、毎回背景を塗り替えて、直前の画像たちを消しています。これがないと前の処理たちがそのまま残ってしまいます。
screen.blit(画像,(x, y)):画像の配置で、気を付けないといけないのが、表示画面の左上がx:0、y:0になります。
画面の右下に向かうにつれてxとyの値が大きくなります。

諸々の挙動

ゲーム内の挙動ですが、いくつかピックアップして解説します。

from random import randint

self.balloon_x = randint(500, 700)

randint(最小値, 最大値)。これにより風船が出現するx座標をランダムにしています。intなので設定値内の整数からランダムで選ばれます。

import sys

sysfont = pygame.font.SysFont(None, 36)

        while True:
            score_image = sysfont.render("score : {}".format(self.score), True, (0, 0, 255))
            screen.blit(score_image, (10, 20))

文字の出力。
pygame.font.Sysfont(name, size, bold, italic)と4つのパラメータを変えることが出来ます。
sysfont.renderの{ }の中にはformat()が入ります。

    def set_balloon(self):
        now_time = time.time()
        if self.is_collision:
            if now_time - self.balloon_break > 1:
                self.is_collision = False
        if self.is_collision is False:
            if self.is_balloon:
                self.balloon_y = self.balloon_y - 12
                self.balloon_alive = time.time()
                if self.balloon_alive - self.balloon_create > 4:
                    self.is_balloon = False
                else:
                    self.collision_check()
            else:
                self.balloon_image = pygame.image.load("balloon-red.png")
                self.balloon_x = randint(500, 700)
                self.balloon_y = 600
                self.is_balloon = True
                self.balloon_create = time.time()

あと、ごにょごにょ書いているset_balloon関数なんですが、ここではtimeモジュールを利用しています。
風船が出現したときに、balloon_createとして現在時刻を取得し、その後、balloon_aliveという時間を取得し続けて、差が4秒以上になれば再び風船を出現させるようにしています。
ついでにnow_timeというのも計測して、風船が割れたら1秒後に風船を出現させるようにしています。

n秒後に処理させるって調べるとsleepが出てきますが、あれを使うと処理全体が止まるので注意してください。

import math

    def bullet(self):
        gravity = 9.8
        if self.bullet_shot:
            bullet_speed_x = self.bullet_speed * math.cos(math.radians(self.cannon_radian))
            bullet_speed_y = (self.bullet_speed * math.sin(math.radians(self.cannon_radian)))
            self.bullet_x = self.bullet_x + bullet_speed_x
            self.bullet_y = self.bullet_y - bullet_speed_y + gravity * self.time
            self.time += 0.2

            if self.bullet_x > 800 or self.bullet_y > 600:
                self.bullet_shot = False
                self.set_bullet()

発射した球の挙動は、発射角にサインコサインでそれっぽい動きになるように。
ちゃんとした物理式を使うか迷ったんですが、それっぽい動きになったからヨシっ!

球が画面外に出るまで次の球は発射させません。
※この記述の場合、画面内に球がある状態で発射すると、飛行中の球が消えてしまいます。
 なので、連射したい場合はそれぞれを別のものとして扱う必要があります。

    def collision_check(self):
        distance_y = ((self.balloon_y + 15) - (self.bullet_y + 16))**2
        distance_x = ((self.balloon_x + 20) - (self.bullet_x + 16))**2
        distance = (distance_x + distance_y)**(1/2)
        if distance < 31:
            self.pang_image = pygame.image.load("pang.png")
            self.pang_rect = self.pang_image.get_rect()
            self.pang_rect.center = (self.balloon_x + 20, self.balloon_y + 15)
            self.is_collision = True
            self.is_balloon = False
            self.total_score(100)
            self.balloon_break = time.time()

衝突判定。
今回は球と風船のだいたいの中心からの絶対値で計算しています。久々すぎて三平方の定理ググりました。
ヒットボックスが円だと楽でいい!(四角の場合は、x軸同士とy軸同士の差を絶対値を出してandでうんぬんかんぬん)
衝突すると100点入ります。

終わりに

色々な挙動を覚えるにはやはり触れてみるのが一番ですね。
今回の要素を組み合わせれば、簡単なゲームなら大体できそうな気がしなくもないです。
あと、イラスト描けるようになりたい・・・

コード全体像

import math
import sys
import pygame
from pygame.locals import *
from random import randint
import time

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Game")
sysfont = pygame.font.SysFont(None, 36)
pygame.key.set_repeat(5, 5)
FPSCLOCK = pygame.time.Clock()
FPS = 15


class CanonGame():
    def __init__(self):
        bullet_image = pygame.image.load("bullet.png")
        self.set_cannon()
        self.set_bullet()
        self.bullet_shot = False
        self.is_balloon = False
        self.is_collision = False
        self.score = 0

        while True:
            for event in pygame.event.get():
                if event.type == QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == KEYDOWN:
                    if event.key == K_SPACE:
                        self.bullet_shot = True
                    elif event.key == K_UP and self.cannon_radian < 89:
                        self.cannon_radian += 1
                    elif event.key == K_DOWN and self.cannon_radian > 0:
                        self.cannon_radian -= 1

            self.set_balloon()
            self.bullet()

            screen.fill((255, 255, 255))
            screen.blit(bullet_image, (self.bullet_x, self.bullet_y))
            if not self.is_collision:
                screen.blit(self.balloon_image, (self.balloon_x, self.balloon_y))
            elif self.is_collision:
                screen.blit(self.pang_image, self.pang_rect)
            rotate_cannon = pygame.transform.rotate(self.cannon_image, self.cannon_radian - 45)
            cannon_rect = rotate_cannon.get_rect()
            cannon_rect.center = (84, 536)
            screen.blit(rotate_cannon, cannon_rect)
            score_image = sysfont.render("score : {}".format(self.score), True, (0, 0, 255))
            screen.blit(score_image, (10, 20))

            pygame.display.update()
            FPSCLOCK.tick(FPS)

    def set_bullet(self):
        self.bullet_x = 68
        self.bullet_y = 520
        self.bullet_speed = 50
        self.time = 0

    def set_cannon(self):
        self.cannon_image = pygame.image.load("cannon.png")
        self.cannon_radian = 45

    def set_balloon(self):
        now_time = time.time()
        if self.is_collision:
            if now_time - self.balloon_break > 1:
                self.is_collision = False
        if self.is_collision is False:
            if self.is_balloon:
                self.balloon_y = self.balloon_y - 12
                self.balloon_alive = time.time()
                if self.balloon_alive - self.balloon_create > 4:
                    self.is_balloon = False
                else:
                    self.collision_check()
            else:
                self.balloon_image = pygame.image.load("balloon-red.png")
                self.balloon_x = randint(500, 700)
                self.balloon_y = 600
                self.is_balloon = True
                self.balloon_create = time.time()

    def bullet(self):
        gravity = 9.8
        if self.bullet_shot:
            bullet_speed_x = self.bullet_speed * math.cos(math.radians(self.cannon_radian))
            bullet_speed_y = (self.bullet_speed * math.sin(math.radians(self.cannon_radian)))
            self.bullet_x = self.bullet_x + bullet_speed_x
            self.bullet_y = self.bullet_y - bullet_speed_y + gravity * self.time
            self.time += 0.2

            if self.bullet_x > 800 or self.bullet_y > 600:
                self.bullet_shot = False
                self.set_bullet()

    def collision_check(self):
        distance_y = ((self.balloon_y + 15) - (self.bullet_y + 16))**2
        distance_x = ((self.balloon_x + 20) - (self.bullet_x + 16))**2
        distance = (distance_x + distance_y)**(1/2)
        if distance < 31:
            self.pang_image = pygame.image.load("pang.png")
            self.pang_rect = self.pang_image.get_rect()
            self.pang_rect.center = (self.balloon_x + 20, self.balloon_y + 15)
            self.is_collision = True
            self.is_balloon = False
            self.total_score(100)
            self.balloon_break = time.time()

    def total_score(self, score):
        self.score = self.score + score


def main():
    CanonGame()


if __name__ == '__main__':
    main()
3
2
1

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
3
2