【Pygame Zero】簡単なゲーム03:複数のキャラクターを個別に動かす
こんにちは!
「Pythonしよう!楽しく学べるプログラミング教室」の
ラッチ先生です


スックです。よろしくね!
BGM提供:DOVA-SYNDROME
https://dova-s.jp/
・ 「No_Name 」 by VeryGoodMan
効果音提供:Chisato’s Website
https://chisatosound.sakura.ne.jp/index.html
・ 「Accent. Brilliant [01] (High. Fast)」:アクセント
・ 「Button [06-3] (Cancel)」:ボタン
・ 「Thought Babble/Cloud [02]」:アニメ

基礎プログラムと 画像を入れた
「簡単なゲーム03 コウモリを ねらえ!」zipフォルダを ダウンロードしてください


今回は、スクリーンの色を “silver”に したよ
「原色大事典」サイトには、URL:https://www.colordic.org/
pygame zeroで使える色が載っています

学習の流れ
10匹のコウモリが 飛び回る
コウモリをクリックして 撃退する
15秒以内に 撃退する

BGM・効果音を 入れる
BGM提供:DOVA-SYNDROME
https://dova-s.jp/
・ 「No_Name 」 by VeryGoodMan
効果音提供:Chisato’s Website
https://chisatosound.sakura.ne.jp/index.html
・ 「Accent. Brilliant [01] (High. Fast)」:アクセント
・ 「Button [06-3] (Cancel)」:ボタン
・ 「Thought Babble/Cloud [02]」:アニメ
プログラムを 実行してみよう
プログラミングの仕方を説明します
モジュールを 用意する

今回の「コウモリを ねらえ!」では、
・ コウモリを クリックする
・ コウモリのコスチュームの切り替え
・ セリフを 言う
プログラムがあります。
そこで、3つのモジュールを 用意しました



モジュールとは、
関数やプログラムが書かれているファイルのことだよ

Actor()クラスが入っている変数batには、
コウモリを 動かす属性(データ)やメソッド(命令)が あります。


属性(データ)やメソッド(命令)は、
『 . (ドット)』を付ければ、使えるよ
今回のプログラミングのポイント

今回は、
10匹のコウモリが、別々の動きなるプログラムを作っていきます。


リストbatsを作成して、その中に10匹のコウモリを入れるよ


関数の引数に batオブジェクトを 入れるだけだよ

この3つのステップで
コスチュームを 切り替えることができるよ

bats[:] は、リストbatsのコピー と覚えてね


3つに 分けたよ
10匹のコウモリが 飛び回る

まず、空のリストbatsを作成して、
10匹のコウモリを スクリーンに 表示します。

bats = [] #1 リストbatsに 空リストを 代入するfor i in range(10): #2 10回 繰り返す
x = random.randint(50, WIDTH-50) #3 変数xに 50~750からランダムに決めた数値を 代入する
y = random.randint(50, HEIGHT-50) #4 変数yに 50~550からランダムに決めた数値を 代入する
bat = Actor("bat1", (x, y)) #5 コウモリオブジェクトを 生成する
bats.append(bat) def draw():
screen.fill("silver")
for bat in bats: #6 リストbatsから コウモリを取り出す
bat.draw() #7 コウモリを 表示する
毎回、違うところに 出現するね

コスチュームモジュールを使って、
コウモリの羽を ばたつかせるよ
for i in range(10):
x = random.randint(50, WIDTH-50)
y = random.randint(50, HEIGHT-50)
bat = Actor("bat1", (x, y))
bat.images = ["bat1", "bat2"] #1 プロパティimagesに 2つの画像を 代入する
bat.fps = 20 #2 プロパティfpsに 20に設定する
bats.append(bat) def update():
for bat in bats: #3 リストbatsから batを取り出す
bat.animate() #4 コスチュームを 切り替える
コウモリの羽ばたく速度を変えたい時は、
fps(フレームズ・パー・セコンド)の数値を 変えようね

つぎは、animate( ) 関数を使って
コウモリを バラバラに 動かします。

def bat_move(bat): #1 コウモリの動きを 定義する 引数bat
x = random.randint(50, WIDTH-50) #2 x座標に 50~750からランダムに決めた数値を 代入する
y = random.randint(50, HEIGHT-50) #3 y座標に 50~550からランダムに決めた数値を 代入する
move_time = random.uniform(1, 3) #4 変数move_timeに 1~3からランダムに決めた数値を 代入する
animate(bat, duration = move_time, pos=(x, y), on_finished=lambda:bat_move(bat)) #5 コウモリが 動くアニメーションをする
for bat in bats: #6 リストbatsから batを取り出す
bat_move(bat) #7 bat_move()関数を 実行するポイント1
27. move_time = random.uniform(1, 3)

各コウモリの動きを バラバラにするために、移動する時間を 変えました

28. animate(bat, …, on_finished=lambda:bat_move(bat))
『lambda』って、何?

『lambda』とは、無名関数。
lambda:bat_move(bat) は、
『bat_move(bat) 関数を 実行する」と定義した関数です。


なぜ、ここで『lambda」を使うか 説明しますね
animate( ) 関数のon_finished引数には、
実行させたい関数を渡します。
animate ( bat, ・・・, on_finished = bat_move)
しかし、
これでは、10匹のコウモリを個別に動かすdef bat_move(bat): 関数には
引数batが あるため エラーが発生します。

bat_move(bat) 関数を そのまま入力すれば いいんじゃない…
animate ( bat, ・・・, on_finished = bat_move(bat))
関数名bat_move(bat)に ( )が あるため エラーが発生します。
( )は、関数を実行する という働き。
つまり、
アニメーションする前に bat_move(bat)関数が実行され
永遠ループに 入ります

えっ⁉ ( )は、関数を実行する意味だったんだ‼

そうだよ。
そこで、「lambda」を 使います。

animate ( bat, ・・・, on_finished = lambda:bat_move(bat))
lambda: bat_move(bat) という関数になって
on_finished引数に渡り、アニメーションが 終了後 実行されます。

lambda :によって、
引数(bat)が あっても すぐ実行されなくなったね
ポイント2
for文を使って
各コウモリオブジェクトを bat_move(bat)関数に 渡し 実行します。


これで、コウモリが 別々に 動きます
コウモリを クリックして 撃退する

コウモリにプロパティを2つ追加します。
1つは、「animation」プロパティ
このプロパティに コウモリのアニメーションを 設定。
コウモリのアニメーションを止めることができます

def bat_move(bat):
x = random.randint(50, WIDTH-50)
y = random.randint(50, HEIGHT-50)
move_time = random.uniform(1, 3)
bat.animation = animate(bat, duration = move_time, pos=(x, y), on_finished=lambda:bat_move(bat)) #1 プロパティanimationに amimate( )関数を 設定する
2つは、「hit」プロパティ
このプロパティは、 コウモリのクリック判定

for i in range(10):
x = random.randint(50, WIDTH-50)
y = random.randint(50, HEIGHT-50)
bat = Actor("bat1", (x, y))
bat.hit = False #1 プロパティhitに Falseを設定する
bat.images = ["bat1", "bat2"] 
Actor( )クラスで作られたbatオブジェクトには、
自分でプロパティを 追加できるんだ

collide_pixel( ) メソッドで、コウモリをクリックしたら
・ アニメーションを止める
・ 画像を “bat3″にする
この2つのプログラムを作りましょう


game = "play" #1 変数gameを宣言 初期値:playdef on_mouse_down(pos): #2 マウスがクリックした関数を定義する 引数pos
global game #3 グローバル変数 game
if game == "play": #4 もし 変数gameが playなら
for bat in bats: #5 リストbatsから コウモリを取り出す
if bat.collidepoint_pixel(pos) and not bat.hit: #6 もし コウモリをクリックして かつ プロパティhitが Falseなら
bat.hit = True #7 プロパティhitが Trueに設定する
bat.animation.stop() #8 コウモリのアニメーションを停止する
bat.image = "bat3" #9 コウモリの画像に bat3を代入する
break #10 ループ終了def update():
for bat in bats:
if not bat.hit: #11 もし プロパティhitが Falseなら
bat.animate() 
コウモリをクリックすると 動きが止まったね

sayモジュールを使って
・ クリックされたら セリフを1秒間言う
・ セリフを言い終わったら 削除する
このプログラムを作っていきましょう


is_talking( )メソッドを使って
コウモリがセリフを言い終わったタイミングで 削除するよ


def draw():
screen.fill("silver")
for bat in bats[:]: #3 リストbatsを コピーリストbats[:]にする
bat.draw()
if bat.hit and not bat.is_talking(): #4 もし プロパティhitが Trueで かつ セリフを言ってなければ
bats.remove(bat) #5 リストbatsから batを削除する
text_display.draw(screen) #1 テキストディスプレイを 装備するdef on_mouse_down(pos):
global game
if game == "play":
for bat in bats:
if bat.collidepoint_pixel(pos) and not bat.hit:
bat.hit = True
bat.animation.stop()
bat.image = "bat3"
bat.say("Ouch!", 1, color= "red", size=60, y_offset=-60) #2 セリフを 1秒間言う
break ここで、リストbatsの中のコウモリを削除する場合
for bat in bats[:]で、リストbatsのコピーを使うか
解説します。
もし、リストbatsで コウモリを取り出すと
35. for bat in bats :
38. bats.remove(bat)
リストbatsから コウモリを取り出している最中に 削除すると
コウモリの取り出し方が変になり、 エラーが起きる場合があります。

ああっ!
コウモリ「2」が 飛ばされてるね
ポイント1
リストbatsを コピーして リストbats[:] にする
35. for bat in bats[:]:
38. bats.remove(bat)
元のリストbatsから要素を削除しても、
コピーしたリストbats [:]には影響しません。

なるほどね!
リストbats [ : ] は、
最後のコウモリを取り出してからコピーするんだね
15秒以内に 撃退する


15秒以内に10匹のコウモリを撃退すればクリアです。
・ 変数bg_image : 背景画像を 保存する
・ 変数bat_count : 撃退したコウモリの数を 保存する
2つの変数を作って クリアのプログラムを 作りましょう
bg_image = None #1 変数bg_imageを宣言 初期値:None (無し)
bat_count = 0 #2 変数bat_countを宣言 初期値: 0def draw():
if game == "play": #3 もし 変数gameが playなら
screen.fill("silver")
else: #4 その他は
screen.blit(bg_image, (0, 0)) #5 変数bg_imageに入っている画像を 表示するdef on_mouse_down(pos):
global game, bat_count, bg_image #6 グローバル変数 bat_count, bg_image
if game == "play":
for bat in bats:
if bat.collidepoint_pixel(pos) and not bat.hit:
sounds.button_06_3_cancel.play()
bat.hit = True
bat_count += 1 #7 変数bat_countを 1ずつ増やす
if bat_count == 10: #8 もし 変数bat_countが 10になったら
game = "clear" #9 変数gameに clear代入する
bg_image = "bg_clear" #10 変数bg_imageに 画像bg_clearを 代入する
おお!クリアしたあ

つぎは、ゲームオーバーのプログラムです。
game_over( )関数で、
・ 背景画を “bg_over“に する
・ コウモリの動きを 止める
・ 「Too Bad!(残念!)」と 言う
ゲームオーバーの処理を 定義します
schedule( )メソッドを使って、
15秒後に game_over( )関数を実行します

def on_mouse_down(pos):
global game, bat_count, bg_image
if game == "play":
for bat in bats:
if bat.collidepoint_pixel(pos) and not bat.hit:
sounds.button_06_3_cancel.play()
bat.hit = True
bat_count += 1
if bat_count == 10:
game = "clear"
clock.unschedule(game_over) #10 game_over関数を中止するdef game_over(): #1 game_over( )関数を 定義する
global game, bg_image #2 グローバル変数game bg_imge
game = "over" #3 変数gameに overを代入する
bg_image = "bg_over" #4 変数bg_imageに 画像bg_overを代入する
for bat in bats: #5 リストbatsから コウモリを取り出す
if not bat.hit: #6 もし プロパティhitが Falseなら
bat.animation.stop() #7 プロパティanimationにあるanimate( )関数を 止める
bat.say("Too Bad!", 2, color="blue", size=60, y_offset=-60) #8 「Too Bad!」と2秒言う
clock.schedule(game_over, 15) #9 15秒後に game_over関数を実行する
unschedule( )メソッドを使って
クリアした時は、game_over関数を 中止させるよ

BGM・効果音を 入れる
BGM提供:DOVA-SYNDROME
https://dova-s.jp/
・ 「No_Name 」 by VeryGoodMan
効果音提供:Chisato’s Website
https://chisatosound.sakura.ne.jp/index.html
・ 「Accent. Brilliant [01] (High. Fast)」:アクセント
・ 「Button [06-3] (Cancel)」:ボタン
・ 「Thought Babble/Cloud [02]」:アニメ

それでは、BGMを 入れてみましょう
Python zeroには、musicオブジェクトが標準装備であります。
やり方、以下の手順です
BGM の 流し方
1. MP3ファイルのBGMを 用意する

☆ Pygame zeroでは英語の大文字が 使えません。 エラーが出ます
小文字 に直します
2. フォルダ『music』フォルダに 入れる

3. musicモジュールのメソッドを使う

music.play("no_name") #1 BGMを 入れる
今回のプログラムに「DOVA-SYNDROME」サイトから
・ 「No_Name 」 by VeryGoodMan
BGMの曲として お借りしました。 ありがとうございます。

つぎに
効果音をつけましょう!
・ コウモリを クリックした時
・ クリア
・ ゲームオーバー
Python zeroには、soundsオブジェクトが標準装備であります。
次の手順で 行います
効果音のつけ方
1. WAVファイルの効果音を 用意する



2. フォルダ『sounds』フォルダに 入れる

3. soundsモジュールのplay( )メソッドを使う


今回のプログラムに
「Chisato’s Website」サイトから
・ 「accent_brilliant_01_high_fast」:アクセント
・ 「button_06_3_cancel」:ボタン
・ 「thought_bubble_cloud_02」:アニメ
効果音を お借りしました。 ありがとうございます。
def on_mouse_down(pos):
global game, bat_count, bg_image
if game == "play":
for bat in bats:
if bat.collidepoint_pixel(pos) and not bat.hit:
sounds.button_06_3_cancel.play() #1 コウモリにクリックした効果音を入れる
bat.hit = True
bat_count += 1
if bat_count == 10:
game = "clear"
sounds.accent_brilliant_01_high_fast.play() #2 クリアの効果音を入れる
clock.unschedule(game_over)
bg_image = "bg_clear" def game_over():
global game, bg_image
game = "over"
bg_image = "bg_over"
sounds.thought_bubble_cloud_02.play() #3 ゲームオーバーの効果音を入れる
今回の学習は、これで 終了! おつかれさま
まとめ

今回は、
10匹のコウモリが個別に動くプログラムを 作りました。
import pgzrun
from pgzhelper import *
from costume import *
from say import text_display
import random
WIDTH = 800
HEIGHT = 600
bats = []
game = "play"
bg_image = None
bat_count = 0
for i in range(10):
x = random.randint(50, WIDTH-50)
y = random.randint(50, HEIGHT-50)
bat = Actor("bat1", (x, y))
bat.hit = False
bat.images = ["bat1", "bat2"]
bat.fps = 20
bats.append(bat)
def bat_move(bat):
x = random.randint(50, WIDTH-50)
y = random.randint(50, HEIGHT-50)
move_time = random.uniform(1, 3)
bat.animation = animate(bat, duration = move_time, pos=(x, y), on_finished=lambda:bat_move(bat))
for bat in bats:
bat_move(bat)
def draw():
if game == "play":
screen.fill("silver")
else:
screen.blit(bg_image, (0, 0))
for bat in bats[:]:
bat.draw()
if bat.hit and not bat.is_talking():
bats.remove(bat)
text_display.draw(screen)
def on_mouse_down(pos):
global game, bat_count, bg_image
if game == "play":
for bat in bats:
if bat.collidepoint_pixel(pos) and not bat.hit:
sounds.button_06_3_cancel.play()
bat.hit = True
bat_count += 1
if bat_count == 10:
game = "clear"
sounds.accent_brilliant_01_high_fast.play()
clock.unschedule(game_over)
bg_image = "bg_clear"
bat.animation.stop()
bat.image = "bat3"
bat.say("Ouch!", 1, color= "red", size=60, y_offset=-60)
break
def game_over():
global game, bg_image
game = "over"
bg_image = "bg_over"
sounds.thought_bubble_cloud_02.play()
for bat in bats:
if not bat.hit:
bat.animation.stop()
bat.say("Too Bad!", 2, color="blue", size=60, y_offset=-60)
clock.schedule(game_over, 15)
def update():
for bat in bats:
if not bat.hit:
bat.animate()
music.play("no_name")
pgzrun.go()
引数batに異なるコウモリを渡すことで、
同じ関数でも個別の動作が 実現できます。


たくさんのオブジェクトを動かすゲームで使えるね
それじゃ、またね!
