【AI + pygame】pygameで作るインベーダー風ゲーム 第3回 改造編その3(残機制とステージクリア制の導入・UFO)

公開:2019年04月23日

(2019年4月22日更新)

エンジニアのMです。
pygame によるインベーダー風ゲームの改造記事の第3回を書いていきたいと思います。

今まではエイリアンを全滅させるとそこでゲーム終了でしたが、次のステージに移行してゲームが続くようにしましょう。
同時に残機の概念を導入して、ハイスコアを目指すゲームに改造します。
今回は変更点が多く、コードが長めです。

改造後のコードと画像データは、こちらからダウンロードできます。
-> pygame_invader_3

残機制を導入する

残機制を導入するために必要なものは、

  • 残機を保存する変数
  • 被弾時の処理変更
  • 被弾時の無敵時間

以上の3点です。まとめて見ていきましょう。

[code lang=”python” highlight=”3,13,21-27,35-39″]
class Invader:
def __init__(self):
self.lives = 5 # 残機数
pygame.init()
# 略

def init_game(self):
"""ゲームオブジェクトを初期化"""
# ゲーム状態
self.game_state = START
# スプライトグループを作成して登録
self.all = pygame.sprite.RenderUpdates()
self.invisible = pygame.sprite.RenderUpdates()
self.aliens = pygame.sprite.Group() # エイリアングループ
self.shots = pygame.sprite.Group() # ミサイルグループ
self.beams = pygame.sprite.Group() # ビームグループ
self.walls = pygame.sprite.Group() # 壁グループ
self.ufos = pygame.sprite.Group() # UFOグループ
# デフォルトスプライトグループを登録
Player.containers = self.all
Shot.containers = self.all, self.shots, self.invisible
Alien.containers = self.all, self.aliens, self.invisible
Beam.containers = self.all, self.beams, self.invisible
Wall.containers = self.all, self.walls, self.invisible
UFO.containers = self.all, self.ufos, self.invisible
Explosion.containers = self.all, self.invisible
ExplosionWall.containers = self.all, self.invisible

def draw(self, screen):
"""描画"""
screen.fill((0, 0, 0))
if self.game_state == START: # スタート画面
# 略
elif self.game_state == PLAY: # ゲームプレイ画面
# 無敵時間中は自機が点滅する
if self.player.invisible % 10 > 4:
self.invisible.draw(screen)
else:
self.all.draw(screen)
# 壁の耐久力描画
shield_font = pygame.font.SysFont(None, 30)
for wall in self.walls:
shield = shield_font.render(str(wall.shield), False, (0,0,0))
text_size = shield_font.size(str(wall.shield))
screen.blit(shield, (wall.rect.center[0]-text_size[0]//2,
wall.rect.center[1]-text_size[1]//2))
[/code]
[code lang=”python” highlight=”15,23-35,41″]
class Invader:
def key_handler(self):
"""キーハンドラー"""
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_SPACE:
if self.game_state == START: # スタート画面でスペースを押したとき
self.game_state = PLAY
elif self.game_state == GAMEOVER: # ゲームオーバー画面でスペースを押したとき
self.lives = 5
self.init_game() # ゲームを初期化して再開
self.game_state = PLAY

def collision_detection(self):
"""衝突判定"""
# 略
# プレイヤーとビームの衝突判定
# 無敵時間中なら判定せずに無敵時間を1減らす
if self.player.invisible > 0:
beam_collided = False
self.player.invisible -= 1
else:
beam_collided = pygame.sprite.spritecollide(self.player, self.beams, True)
if beam_collided: # プレイヤーと衝突したビームがあれば
Player.bomb_sound.play()
Explosion(self.player.rect.center)
self.lives -= 1
self.player.invisible = 180 # 無敵時間は3秒
if self.lives < 0:
self.game_state = GAMEOVER # ゲームオーバー!

class Player(pygame.sprite.Sprite):
"""自機"""
speed = 5 # 移動速度
reload_time = 15 # リロード時間
invisible = 0 # 無敵時間
# 略
[/code]
残機数は lives 変数を用意して対応します。
無敵時の描画に使うために invisible というグループを作成しています。
これに Player 以外を登録して、自機だけ描画されないようにします。
後は draw 内で all と invisible を使い分けて、自機が点滅しているように見せれば良いです。

あまりスマートなやり方では無いですが、self.all に手を加えないためにこういう方法になりました。
Player クラスの画像を操作する手もあるかと思います。
任意のスプライトを点滅させるのであれば、各スプライトのクラスに点滅処理を実装するほうが良いでしょう(描画グループでは、グループ単位の対応になるため)。
各スプライトに共通部分が多い時は、

[code lang=”python”]
class SpriteBaseClass(pygame.sprite.Sprite):
# 全スプライトに共通する処理

class NewSpriteClass(SpriteBaseClass):
# スプライト毎に固有の処理
[/code]

のように実装すると、コンパクトに記述できると思います。参考にしてください。

ステージクリア制の導入と情報表示

ステージクリア制の導入と併せてスコア加算の仕組みを導入して、画面上部に表示するようにしましょう。
ステージが進むに連れて難易度を上げられるように、各スプライトのクラスにも変更を加えます。
変更点は以下の通りです。

  • 敵を全滅させたらステージクリア画面に遷移する
  • ステージが進むと敵の攻撃頻度が上がり、移動も高速化する
  • 敵の攻撃が激しくなるのに合わせて、壁の耐久力も少しづつ上げる
  • 画面上部にスコアや残機などの情報を表示する
  • ゲームオーバー時に全て初期化する

エイリアンが次々襲撃してくるイメージに合わせて、ステージ数の数え方は「Wave」とします。

[code lang=”python” highlight=”1,7-8,19,24,32,42-46,51-67,82-83,87-91,99″]
START, PLAY, GAMEOVER, STAGECLEAR = (0, 1, 2, 3) # ゲーム状態
SCR_RECT = Rect(0, 0, 640, 480)

class Invader:
def __init__(self):
self.lives = 5 # 残機数
self.wave = 1 # Wave数
self.score = 0 # スコア(エイリアン 10点、UFO 50点にWave数を乗算)
pygame.init()
# 略

def init_game(self):
"""ゲームオブジェクトを初期化"""
# 略
# エイリアンを作成
for i in range(0, 50):
x = 20 + (i % 10) * 40
y = 50 + (i // 10) * 40
Alien((x,y), self.wave)
# 壁を作成
for i in range(4):
x = 95 + i * 150
y = 400
Wall((x, y), self.wave)

def update(self):
"""ゲーム状態の更新"""
if self.game_state == PLAY:
# 略
# エイリアンをすべて倒したら次のステージへ
if len(self.aliens.sprites()) == 0:
self.game_state = STAGECLEAR

def draw(self, screen):
"""描画"""
screen.fill((0, 0, 0))
if self.game_state == START: # スタート画面
# 略
elif self.game_state == PLAY: # ゲームプレイ画面
# 無敵時間中は自機が点滅する
# 略
# wave数と残機数を描画
stat_font = pygame.font.SysFont(None, 20)
stat = stat_font.render("Wave:{:2d} Lives:{:2d} Score:{:05d}".format(
self.wave, self.lives, self.score), False, (255,255,255))
screen.blit(stat, ((SCR_RECT.width – stat.get_width()) // 2, 10))
# 壁の耐久力描画
# 略
elif self.game_state == GAMEOVER: # ゲームオーバー画面
# 略
elif self.game_state == STAGECLEAR: # ステージクリア画面
# wave数と残機数を描画
stat_font = pygame.font.SysFont(None, 20)
stat = stat_font.render("Wave:{:2d} Lives:{:2d} Score:{:05d}".format(
self.wave, self.lives, self.score), False, (255,255,255))
screen.blit(stat, ((SCR_RECT.width – stat.get_width()) // 2, 10))
# STAGE CLEARを描画
gameover_font = pygame.font.SysFont(None, 80)
gameover = gameover_font.render("STAGE CLEAR", False, (255,0,0))
screen.blit(gameover, ((SCR_RECT.width-gameover.get_width())//2, 100))
# エイリアンを描画
alien_image = Alien.images[0]
screen.blit(alien_image, ((SCR_RECT.width-alien_image.get_width())//2, 200))
# PUSH SPACEを描画
push_font = pygame.font.SysFont(None, 40)
push_space = push_font.render("PUSH SPACE KEY", False, (255,255,255))
screen.blit(push_space, ((SCR_RECT.width-push_space.get_width())//2, 300))

def key_handler(self):
"""キーハンドラー"""
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_SPACE:
if self.game_state == START: # スタート画面でスペースを押したとき
self.game_state = PLAY
elif self.game_state == GAMEOVER: # ゲームオーバー画面でスペースを押したとき
self.score = 0 # スコア初期化
self.wave = 1
self.lives = 5
self.init_game() # ゲームを初期化して再開
self.game_state = PLAY
elif self.game_state == STAGECLEAR:
self.wave += 1
self.lives += 1
self.init_game() # ゲームを初期化して再開
self.game_state = PLAY

def collision_detection(self):
"""衝突判定"""
# エイリアンとミサイルの衝突判定
alien_collided = pygame.sprite.groupcollide(self.aliens, self.shots, True, True)
for alien in alien_collided.keys():
Alien.kill_sound.play()
self.score += 10 * self.wave
Explosion(alien.rect.center) # エイリアンの中心で爆発
# 略
[/code]

新たな状態として STAGECLEAR を追加し、処理を用意します。
Wave数などのパラメータの初期化や設定については、self.init_game を変更しない形で実装しました。
もっと設定し直すパラメータが多ければ、新たにメソッドを用意したほうが良いでしょう。

情報表示やステージクリア画面の描画については、元のコードを参考に位置やフォントサイズを調整しています。
文字列の format メソッドで、流し込む変数の桁数調整、0フィルといった整形をします。

pygame の文字列描画は、

フォント指定 -> render メソッドで画像化 -> screen.blit で描画

という手順が必要で少し面倒ですが、古いゲームのように画像フォントを用意するよりは楽です。

次に各クラスの細かい修正も確認しておきましょう。

[code lang=”python” highlight=”4,7,12,18″]
class Alien(pygame.sprite.Sprite):
"""エイリアン"""
def __init__(self, pos, wave):
self.speed = 1 + wave # 移動速度
self.animcycle = 18 # アニメーション速度
self.frame = 0
self.prob_beam = (1.5 + wave) * 0.002 # ビームを発射する確率
# 略

class Shot(pygame.sprite.Sprite):
"""プレイヤーが発射するミサイル"""
speed = 12 # 移動速度
# 略

class Wall(pygame.sprite.Sprite):
"""ミサイル・ビームを防ぐ壁"""
def __init__(self, pos, wave):
self.shield = 80 + 20 * wave # 耐久力
# 略
[/code]

Alien, Wall クラスにWave数を渡せるようにして、速度や発射確率を計算させます。
色々と変更するついでに、プレイヤーの放つミサイルを少し早くしました。
ステージが進むと敵の動きがどんどん早くなるので、当てやすくなるようにという調整です。

UFOを飛ばす

インベーダー風ゲームらしく、UFOが飛んでくるようにしましょう。
UFOを撃墜すると多く点数が入り、残機が増えるという仕様にします。
また、UFOを時間経過で登場させるためにカウンターを用意する必要があります。

[code lang=”python” highlight=”5,19,26,33-36,47-53″]
class Invader:
def __init__(self):
self.lives = 5 # 残機数
self.wave = 1 # Wave数
self.counter = 0 # タイムカウンター(60カウント=1秒)
# 略

def init_game(self):
"""ゲームオブジェクトを初期化"""
# ゲーム状態
self.game_state = START
# スプライトグループを作成して登録
self.all = pygame.sprite.RenderUpdates()
self.invisible = pygame.sprite.RenderUpdates()
self.aliens = pygame.sprite.Group() # エイリアングループ
self.shots = pygame.sprite.Group() # ミサイルグループ
self.beams = pygame.sprite.Group() # ビームグループ
self.walls = pygame.sprite.Group() # 壁グループ
self.ufos = pygame.sprite.Group() # UFOグループ
# デフォルトスプライトグループを登録
Player.containers = self.all
Shot.containers = self.all, self.shots, self.invisible
Alien.containers = self.all, self.aliens, self.invisible
Beam.containers = self.all, self.beams, self.invisible
Wall.containers = self.all, self.walls, self.invisible
UFO.containers = self.all, self.ufos, self.invisible
Explosion.containers = self.all, self.invisible
ExplosionWall.containers = self.all, self.invisible

def update(self):
"""ゲーム状態の更新"""
if self.game_state == PLAY:
# UFOの出現判定(15秒後に出現する)
self.counter += 1
if self.counter == 900:
UFO((20, 30), self.wave)
# 略

def collision_detection(self):
"""衝突判定"""
# エイリアンとミサイルの衝突判定
alien_collided = pygame.sprite.groupcollide(self.aliens, self.shots, True, True)
for alien in alien_collided.keys():
Alien.kill_sound.play()
self.score += 10 * self.wave
Explosion(alien.rect.center) # エイリアンの中心で爆発
# UFOとミサイルの衝突判定
ufo_collided = pygame.sprite.groupcollide(self.ufos, self.shots, True, True)
for ufo in ufo_collided.keys():
Alien.kill_sound.play()
self.score += 50 * self.wave
Explosion(ufo.rect.center)
self.lives += 1
# 略
[/code]

UFO クラスはAlien クラスをベースに実装します。
random() を使い 0~1 のランダムな値を取得して、出現位置の左右を決定します。

[code lang=”python” highlight=””]
class UFO(pygame.sprite.Sprite):
"""UFO"""
def __init__(self, pos, wave):
self.speed = 1 + wave//2 # 移動速度
# side => 0: left, 1: right
side = 0 if random.random() < 0.5 else 1
if side:
self.speed *= -1 # 右から出現する場合、速度を反転する
self.animcycle = 18 # アニメーション速度
self.frame = 0
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.center = (SCR_RECT.width – pos[0] if side else pos[0], pos[1]) # 開始位置(x)
self.pos_kill = pos[0] if side else SCR_RECT.width – pos[0] # 消滅位置(x)
def update(self):
# 横方向への移動
self.rect.move_ip(self.speed, 0)
# 指定位置まで来たら消滅
if (self.rect.center[0] > self.pos_kill and self.speed > 0) or \
(self.rect.center[0] < self.pos_kill and self.speed < 0):
self.kill()
# キャラクターアニメーション
self.frame += 1
self.image = self.images[int(self.frame//self.animcycle%2)]
[/code]

画面端で出現して、反対の端に到達したら消滅するようにします。
基本的には Alien クラスと変わらないので、特に難しくはないでしょう。

まとめ

記事構成の都合上、同じ部分のコードが何度も登場することになってしまったので、コードの全体像が掴みづらかったのではないかと思います。
コメントは多めに記述するようにしていますので、コードをダウンロードして全体像を確認してみてください。
次回は強化学習のための下準備を進めていきます。

今回のデータはこちらから(再掲) -> pygame_invader_3.zip

[code lang=”python” title=”Invader3_All_code” collapse=”true”]
#!/usr/bin/env python
#coding: utf-8
import pygame
from pygame.locals import *
import os
import random
import sys

START, PLAY, GAMEOVER, STAGECLEAR = (0, 1, 2, 3) # ゲーム状態
SCR_RECT = Rect(0, 0, 640, 480)

class Invader:
def __init__(self):
self.lives = 5 # 残機数
self.wave = 1 # Wave数
self.counter = 0 # タイムカウンター(60カウント=1秒)
self.score = 0 # スコア(エイリアン 10点、UFO 50点にWave数を乗算)
pygame.init()
screen = pygame.display.set_mode(SCR_RECT.size)
pygame.display.set_caption("Invader Part3")
# 素材のロード
self.load_images()
self.load_sounds()
# ゲームオブジェクトを初期化
self.init_game()
# メインループ開始
clock = pygame.time.Clock()
while True:
clock.tick(60)
self.update()
self.draw(screen)
pygame.display.update()
self.key_handler()

def init_game(self):
"""ゲームオブジェクトを初期化"""
# ゲーム状態
self.game_state = START
# スプライトグループを作成して登録
self.all = pygame.sprite.RenderUpdates()
self.invisible = pygame.sprite.RenderUpdates()
self.aliens = pygame.sprite.Group() # エイリアングループ
self.shots = pygame.sprite.Group() # ミサイルグループ
self.beams = pygame.sprite.Group() # ビームグループ
self.walls = pygame.sprite.Group() # 壁グループ
self.ufos = pygame.sprite.Group() # UFOグループ
# デフォルトスプライトグループを登録
Player.containers = self.all
Shot.containers = self.all, self.shots, self.invisible
Alien.containers = self.all, self.aliens, self.invisible
Beam.containers = self.all, self.beams, self.invisible
Wall.containers = self.all, self.walls, self.invisible
UFO.containers = self.all, self.ufos, self.invisible
Explosion.containers = self.all, self.invisible
ExplosionWall.containers = self.all, self.invisible
# 自機を作成
self.player = Player()
# エイリアンを作成
for i in range(0, 50):
x = 20 + (i % 10) * 40
y = 50 + (i // 10) * 40
Alien((x,y), self.wave)
# 壁を作成
for i in range(4):
x = 95 + i * 150
y = 400
Wall((x, y), self.wave)

def update(self):
"""ゲーム状態の更新"""
if self.game_state == PLAY:
# UFOの出現判定(15秒後に出現する)
self.counter += 1
if self.counter == 900:
UFO((20, 30), self.wave)

self.all.update()
# エイリアンの方向転換判定
turn_flag = False
for alien in self.aliens:
if (alien.rect.center[0] < 15 and alien.speed < 0) or \
(alien.rect.center[0] > SCR_RECT.width-15 and alien.speed > 0):
turn_flag = True
break
if turn_flag:
for alien in self.aliens:
alien.speed *= -1
# エイリアンの追加ビーム判定(プレイヤーが近くにいると反応する)
for alien in self.aliens:
alien.shoot_extra_beam(self.player.rect.center[0], 32, 2)
# ミサイルとエイリアン、壁の衝突判定
self.collision_detection()
# エイリアンをすべて倒したら次のステージへ
if len(self.aliens.sprites()) == 0:
self.game_state = STAGECLEAR

def draw(self, screen):
"""描画"""
screen.fill((0, 0, 0))
if self.game_state == START: # スタート画面
# タイトルを描画
title_font = pygame.font.SysFont(None, 80)
title = title_font.render("INVADER GAME", False, (255,0,0))
screen.blit(title, ((SCR_RECT.width-title.get_width())//2, 100))
# エイリアンを描画
alien_image = Alien.images[0]
screen.blit(alien_image, ((SCR_RECT.width-alien_image.get_width())//2, 200))
# PUSH STARTを描画
push_font = pygame.font.SysFont(None, 40)
push_space = push_font.render("PUSH SPACE KEY", False, (255,255,255))
screen.blit(push_space, ((SCR_RECT.width-push_space.get_width())//2, 300))
# クレジットを描画
credit_font = pygame.font.SysFont(None, 20)
credit = credit_font.render("2019 http://pygame.skr.jp", False, (255,255,255))
screen.blit(credit, ((SCR_RECT.width-credit.get_width())//2, 380))
elif self.game_state == PLAY: # ゲームプレイ画面
# 無敵時間中は自機が点滅する
if self.player.invisible % 10 > 4:
self.invisible.draw(screen)
else:
self.all.draw(screen)
# wave数と残機数を描画
stat_font = pygame.font.SysFont(None, 20)
stat = stat_font.render("Wave:{:2d} Lives:{:2d} Score:{:05d}".format(
self.wave, self.lives, self.score), False, (255,255,255))
screen.blit(stat, ((SCR_RECT.width – stat.get_width()) // 2, 10))
# 壁の耐久力描画
shield_font = pygame.font.SysFont(None, 30)
for wall in self.walls:
shield = shield_font.render(str(wall.shield), False, (0,0,0))
text_size = shield_font.size(str(wall.shield))
screen.blit(shield, (wall.rect.center[0]-text_size[0]//2,
wall.rect.center[1]-text_size[1]//2))
elif self.game_state == GAMEOVER: # ゲームオーバー画面
# GAME OVERを描画
gameover_font = pygame.font.SysFont(None, 80)
gameover = gameover_font.render("GAME OVER", False, (255,0,0))
screen.blit(gameover, ((SCR_RECT.width-gameover.get_width())//2, 100))
# エイリアンを描画
alien_image = Alien.images[0]
screen.blit(alien_image, ((SCR_RECT.width-alien_image.get_width())//2, 200))
# PUSH SPACEを描画
push_font = pygame.font.SysFont(None, 40)
push_space = push_font.render("PUSH SPACE KEY", False, (255,255,255))
screen.blit(push_space, ((SCR_RECT.width-push_space.get_width())//2, 300))

elif self.game_state == STAGECLEAR: # ステージクリア画面
# wave数と残機数を描画
stat_font = pygame.font.SysFont(None, 20)
stat = stat_font.render("Wave:{:2d} Lives:{:2d} Score:{:05d}".format(
self.wave, self.lives, self.score), False, (255,255,255))
screen.blit(stat, ((SCR_RECT.width – stat.get_width()) // 2, 10))
# STAGE CLEARを描画
gameover_font = pygame.font.SysFont(None, 80)
gameover = gameover_font.render("STAGE CLEAR", False, (255,0,0))
screen.blit(gameover, ((SCR_RECT.width-gameover.get_width())//2, 100))
# エイリアンを描画
alien_image = Alien.images[0]
screen.blit(alien_image, ((SCR_RECT.width-alien_image.get_width())//2, 200))
# PUSH SPACEを描画
push_font = pygame.font.SysFont(None, 40)
push_space = push_font.render("PUSH SPACE KEY", False, (255,255,255))
screen.blit(push_space, ((SCR_RECT.width-push_space.get_width())//2, 300))

def key_handler(self):
"""キーハンドラー"""
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_SPACE:
if self.game_state == START: # スタート画面でスペースを押したとき
self.game_state = PLAY
elif self.game_state == GAMEOVER: # ゲームオーバー画面でスペースを押したとき
self.score = 0 # スコア初期化
self.wave = 1
self.lives = 5
self.counter = 0
self.init_game() # ゲームを初期化して再開
self.game_state = PLAY
elif self.game_state == STAGECLEAR:
self.wave += 1
self.lives += 1
self.counter = 0
self.init_game() # ゲームを初期化して再開
self.game_state = PLAY

def collision_detection(self):
"""衝突判定"""
# エイリアンとミサイルの衝突判定
alien_collided = pygame.sprite.groupcollide(self.aliens, self.shots, True, True)
for alien in alien_collided.keys():
Alien.kill_sound.play()
self.score += 10 * self.wave
Explosion(alien.rect.center) # エイリアンの中心で爆発
# UFOとミサイルの衝突判定
ufo_collided = pygame.sprite.groupcollide(self.ufos, self.shots, True, True)
for ufo in ufo_collided.keys():
Alien.kill_sound.play()
self.score += 50 * self.wave
Explosion(ufo.rect.center)
self.lives += 1
# プレイヤーとビームの衝突判定
# 無敵時間中なら判定せずに無敵時間を1減らす
if self.player.invisible > 0:
beam_collided = False
self.player.invisible -= 1
else:
beam_collided = pygame.sprite.spritecollide(self.player, self.beams, True)
if beam_collided: # プレイヤーと衝突したビームがあれば
Player.bomb_sound.play()
Explosion(self.player.rect.center)
self.lives -= 1
self.player.invisible = 180 # 無敵時間は3秒
if self.lives < 0:
self.game_state = GAMEOVER # ゲームオーバー!
# 壁とミサイル、ビームの衝突判定
hit_dict = pygame.sprite.groupcollide(self.walls, self.shots, False, True)
hit_dict.update(pygame.sprite.groupcollide(self.walls, self.beams, False, True))
for hit_wall in hit_dict:
hit_wall.shield -= len(hit_dict[hit_wall])
for hit_beam in hit_dict[hit_wall]:
Alien.kill_sound.play()
Explosion(hit_beam.rect.center) # ミサイル・ビームの当たった場所で爆発
if hit_wall.shield <= 0:
hit_wall.kill()
Alien.kill_sound.play()
ExplosionWall(hit_wall.rect.center) # 壁の中心で爆発

def load_images(self):
"""イメージのロード"""
# スプライトの画像を登録
Player.image = load_image("player.png")
Shot.image = load_image("shot.png")
Alien.images = split_image(load_image("alien.png"), 2)
UFO.images = split_image(load_image("ufo.png"), 2)
Beam.image = load_image("beam.png")
Wall.image = load_image("wall.png")
Explosion.images = split_image(load_image("explosion.png"), 16)
ExplosionWall.images = split_image(load_image("explosion2.png"), 16)

def load_sounds(self):
"""サウンドのロード"""
Alien.kill_sound = load_sound("kill.wav")
Player.shot_sound = load_sound("shot.wav")
Player.bomb_sound = load_sound("bomb.wav")

class Player(pygame.sprite.Sprite):
"""自機"""
speed = 5 # 移動速度
reload_time = 15 # リロード時間
invisible = 0 # 無敵時間
def __init__(self):
# imageとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.rect = self.image.get_rect()
self.rect.bottom = SCR_RECT.bottom # プレイヤーが画面の一番下
self.reload_timer = 0
def update(self):
# 押されているキーをチェック
pressed_keys = pygame.key.get_pressed()
# 押されているキーに応じてプレイヤーを移動
if pressed_keys[K_LEFT]:
self.rect.move_ip(-self.speed, 0)
elif pressed_keys[K_RIGHT]:
self.rect.move_ip(self.speed, 0)
self.rect.clamp_ip(SCR_RECT)
# ミサイルの発射
if pressed_keys[K_SPACE]:
# リロード時間が0になるまで再発射できない
if self.reload_timer > 0:
# リロード中
self.reload_timer -= 1
else:
# 発射!!!
Player.shot_sound.play()
Shot(self.rect.center) # 作成すると同時にallに追加される
self.reload_timer = self.reload_time

class Alien(pygame.sprite.Sprite):
"""エイリアン"""
def __init__(self, pos, wave):
self.speed = 1 + wave # 移動速度
self.animcycle = 18 # アニメーション速度
self.frame = 0
self.prob_beam = (1.5 + wave) * 0.002 # ビームを発射する確率
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.center = pos

def update(self):
# 横方向への移動
self.rect.move_ip(self.speed, 0)
# ビームを発射
if random.random() < self.prob_beam:
Beam(self.rect.center)
# キャラクターアニメーション
self.frame += 1
self.image = self.images[self.frame//self.animcycle%2]

def shoot_extra_beam(self, target_x_pos, border_dist, rate):
if random.random() < self.prob_beam*rate and \
abs(self.rect.center[0] – target_x_pos) < border_dist:
Beam(self.rect.center)

class UFO(pygame.sprite.Sprite):
"""UFO"""
def __init__(self, pos, wave):
self.speed = 1 + wave//2 # 移動速度
# side => 0: left, 1: right
side = 0 if random.random() < 0.5 else 1
if side:
self.speed *= -1 # 右から出現する場合、速度を反転する
self.animcycle = 18 # アニメーション速度
self.frame = 0
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.center = (SCR_RECT.width – pos[0] if side else pos[0], pos[1]) # 開始位置(x)
self.pos_kill = pos[0] if side else SCR_RECT.width – pos[0] # 消滅位置(x)
def update(self):
# 横方向への移動
self.rect.move_ip(self.speed, 0)
# 指定位置まで来たら消滅
if (self.rect.center[0] > self.pos_kill and self.speed > 0) or \
(self.rect.center[0] < self.pos_kill and self.speed < 0):
self.kill()
# キャラクターアニメーション
self.frame += 1
self.image = self.images[int(self.frame//self.animcycle%2)]

class Shot(pygame.sprite.Sprite):
"""プレイヤーが発射するミサイル"""
speed = 12 # 移動速度
def __init__(self, pos):
# imageとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.rect = self.image.get_rect()
self.rect.center = pos # 中心座標をposに
def update(self):
self.rect.move_ip(0, -self.speed) # 上へ移動
if self.rect.top < 0: # 上端に達したら除去
self.kill()

class Beam(pygame.sprite.Sprite):
"""エイリアンが発射するビーム"""
speed = 5 # 移動速度
def __init__(self, pos):
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.rect = self.image.get_rect()
self.rect.center = pos
def update(self):
self.rect.move_ip(0, self.speed) # 下へ移動
if self.rect.bottom > SCR_RECT.height: # 下端に達したら除去
self.kill()

class Explosion(pygame.sprite.Sprite):
"""爆発エフェクト"""
animcycle = 2 # アニメーション速度
frame = 0

def __init__(self, pos):
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.rect.center = pos
self.max_frame = len(self.images) * self.animcycle # 消滅するフレーム

def update(self):
# キャラクターアニメーション
self.image = self.images[self.frame//self.animcycle]
self.frame += 1
if self.frame == self.max_frame:
self.kill() # 消滅

class ExplosionWall(Explosion):
pass

class Wall(pygame.sprite.Sprite):
"""ミサイル・ビームを防ぐ壁"""

def __init__(self, pos, wave):
self.shield = 80 + 20 * wave # 耐久力
# imagesとcontainersはmain()でセット
pygame.sprite.Sprite.__init__(self, self.containers)
self.rect = self.image.get_rect()
self.rect.center = pos

def update(self):
pass

def load_image(filename, colorkey=None):
"""画像をロードして画像と矩形を返す"""
filename = os.path.join("data", filename)
try:
image = pygame.image.load(filename)
except pygame.error as message:
print("Cannot load image:", filename)
raise SystemExit(message)
image = image.convert()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image

def split_image(image, n):
"""横に長いイメージを同じ大きさのn枚のイメージに分割
分割したイメージを格納したリストを返す"""
image_list = []
w = image.get_width()
h = image.get_height()
w1 = w // n
for i in range(0, w, w1):
surface = pygame.Surface((w1,h))
surface.blit(image, (0,0), (i,0,w1,h))
surface.set_colorkey(surface.get_at((0,0)), RLEACCEL)
surface.convert()
image_list.append(surface)
return image_list

def load_sound(filename):
"""サウンドをロード"""
filename = os.path.join("data", filename)
return pygame.mixer.Sound(filename)

if __name__ == "__main__":
Invader()
[/code]

フォーム作成クラウドサービス「Formzu(フォームズ)」を運営しているフォームズ株式会社です。
オフィスで働く方、ホームページを運営されている皆様へ
仕事の効率化、ビジネススキル、ITノウハウなど役立つ情報をお届けします。
  • 【初めての方へ】メールフォームについて