##はじめに
前回の投稿では、tkinterを使ったターン制の三目並べを作成したので、今回はpygameを使った常に画面が更新されるものを作ってみました。
タイトルで風船割りゲームと書きましたがそこまでのゲーム性はありません。笑
ただ、今後ゲームを作る際に使えそう?な処理は学べたかなぁ。。。と思います。
例によって、欲しい機能を寄せ集めたキメラティックコードなので読みやすさは今後の課題ですね。
今回の完成品 |
---|
一番最後に今回のコード全体像を載せてあります。 |
##表示画面作成
まずは基盤となる画面作成からですね。
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()