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画像は適当にペイントで描いたものです。
bullet.png:弾。幅8 x 高さ8ピクセル。
disk.png:円盤。幅25 x 高さ8ピクセル。
gun.png:銃。幅17 x 高さ39ピクセル。
凝った画像にすると雰囲気が変わるかもしれません。