『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

Pygame Zero ゲーム開発入門

Pygame Zeroゲーム開発入門

Pygame Zero はプログラミングの教育目的で設計された、Python のゲーム開発用ライブラリです。色々な手続きをバックグラウンドで処理してくれるので、短いコードでゲームを作れるようになっています。実際、ある程度 Python のコードを書ける人であれば、Pygame Zero の習得は決して難しくないはずです。ゲームが好きな人、特に私のようなファミコン世代 (← 年がバレる) にとっては、遊び感覚で楽しみながら学習できると思います。私は数値計算屋で、ゲーム開発が専門ではありませんが夢中になりました。しばらく本業をほったらかしてゲームを作ってました (← 威張ることではない)。皆さんも、ぜひゲームプログラミングを楽しんでください。

それでは早速、Pygame Zero の基本的なコードの書き方を覚えましょう。準備として、Pygame Zero 専用の py ファイルや画像を格納するための mygame フォルダを作成してください。さらに、mygame フォルダの中にゲームで使用する画像を保存するための images フォルダを作っておきます。
 
次にゲームで使用するスプライトの画像を用意します。スプライトとは、ゲーム背景とは別に動く画像(ユーザーが操作する主人公キャラクターや敵の画像)のことです。
 
画像には著作権の問題があるので、適当な所から拾ってくるわけにはいきません。KENNEY というサイトには、無料で利用できる素材がたくさんあるので、必要な画像はこのサイトから調達することにします。
 
この記事では車の画像を使うので、KENNEY サイトの Racing Pack のダウンロードページ に行って、kenney_racingpack_updated.zip をダウンロードします。
 
ダウンロードしたら、このファイルをダブルクリックして開きます (圧縮ファイルをすべて解凍する必要はありません)。PNG、Cars の順に開いて、car_red_small_5.png と car_blue_small_5.png を さきほど作成した images フォルダにドラッグ & ドロップでコピーします。
 
コードの中で短く記述できるように、ファイル名をそれぞれ car_red.png, car_blue.png に変えておきましょう。

スクリーンとスプライトの表示

それでは、最初に用意した車の画像を表示するコードを書いてみます。

# car_01.py

# Pygame Zero をインポート
import pgzrun

WIDTH = 400  # スクリーンの横幅
HEIGHT = 400  # スクリーンの縦幅

# Actorオブジェクトを作成
car = Actor('car_red', center=(200, 300))  # (1)

def draw():
    screen.clear()
    car.draw()  # (2)

pgzrun.go()

WIDTH と HEIGHT はそれぞれスクリーンの横幅と縦幅です。car01.py を実行すると、次のような画面が現れます。
 【Pygame Zero】スクリーンと車の描画
(1) で Actor クラスのオブジェクトを作成しています。
Actor はスプライトを表示させるための組み込みクラスです。Actor() の第 1 引数には表示させる画像を、第 2 引数ではスプライトの座標を指定します。(1) では images フォルダから car_red.png を読み込んで、画像の中心位置を (x, y) = (200, 300) の位置に合わせて表示させています。座標は center 以外にも、下図のようなキーワードで指定できます。
 【Pygame Zero】Actor オブジェクトの位置指定の基準点
(2) のコードで画面に車を表示します。draw() はゲーム実行時に自動的に呼び出される関数です。draw() 関数の内部で Actor.draw() を記述してスプライトを表示する決まりになっています。

画面の更新

ゲーム内で変化のある動きを実現したい場合、update() 関数の中に記述します。update() はゲーム実行時、1 秒間に 60 回呼び出されます。車を動かしてみましょう。car01.py ファイルを次のように修正してみてください。

# car_02.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

def draw():
    screen.clear()
    car.draw()

def update():
    car.y -= 1  # (3)

pgzrun.go()

car_02.py を実行すると、車は上方向にゆっくりと動きます。(3) では Actor オブジェクトの y 属性を (1/60) 秒ごとに 1 減じています(ウインドウの下方向に y 軸をとっているので、y 属性を減らすと上方向に進みます)。

画像の回転

Actor オブジェクトには画像の方向を決める angle 属性があるので、方向を変えるだけの画像を別々に用意する必要はありません。車を回転させてみましょう。

# car_03.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))
car.angle = 90  # (4)

def draw():
    screen.clear()
    car.draw()

pgzrun.go()

car03.py を実行すると、車は左を向きます。
 Actor.angle()によるオブジェクトの回転
(4) は車を反時計回りに 90°回転させるコードです。angle 属性に正の値を入れると、Actor オブジェクトは指定した数値だけ反時計回りに回転します。負の値を入れた場合は時計回りに回転します。
 
update() 関数の中で angle 属性を増減させると、Actor オブジェクトを連続的に回転させることができます。次のコードを実行すると、車が時計回りにゆっくり回転し続けます。

# car_04.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

def draw():
    screen.clear()
    car.draw()

def update():
    car.angle -= 1  # (5)

pgzrun.go()

(5) で画面の更新毎に時計回りに 1°ずつ回転するようにしています。update() は 1 秒間に 60 回呼び出されるので、車は 1 秒間に 60°回転します。

キーボードによる操作

次はキーボードで車を動かしてみましょう。update() 関数の中で、どのキーボードが押されたかによって処理を条件分枝させます。

# car_05.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

def draw():
    screen.clear()
    car.draw()

def update():
    if keyboard.up:  # 上矢印キーが押された場合
        car.angle = 0
        car.y -= 2
    elif keyboard.down:  # 下矢印キーが押された場合
        car.angle = 180
        car.y += 2
    elif keyboard.left:  # 左矢印キーが押された場合
        car.angle = 90
        car.x -= 2
    elif keyboard.right:  # 右矢印キーが押された場合
        car.angle = -90
        car.x += 2

pgzrun.go()

car_05.py を実行して、方向キーで車が自由に移動できることを確認してください。angle 属性を使って、車の動く方向に向きを合わせるようにしてあります。
 
キーボードの入力検知はイベント処理関数 on_key_down() に記述することもできます。

# car_06.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

def draw():
    screen.clear()
    car.draw()

def on_key_down(key):
    if key == keys.UP:  # 上矢印キーが押された場合
        car.angle = 0
        car.y -= car.size[0]
    elif key == keys.DOWN:  # 下矢印キーが押された場合
        car.angle = 180
        car.y += car.size[0]
    elif key == keys.LEFT:  # 左矢印キーが押された場合
        car.angle = 90
        car.x -= car.size[0]
    elif key == keys.RIGHT:  # 右矢印キーが押された場合
        car.angle = -90
        car.x += car.size[0]

pgzrun.go()

on_key_down() は引数 key に keys オブジェクトの属性(アトリビュート)を受け取るように記述します。たとえば、keys.UP は上方向の矢印キーに対応しています。ただし、on_key_down() では、update() のようにキーを押し続けて滑らかに移動させることができないので、実用上は、Actor オブジェクトのサイズ程度の更新幅を設定しておく必要があります。

マウスクリックの検知

次はマウスで車をクリックしたときに動き出すようなコードを書いてみましょう。Pygame Zero では、on_mouse_down() 関数にマウスクリックを検知したときの処理を書き込みます。また、Actor オブジェクトの collidepoint() を使うと、マウスが Actor にヒットしたかどうかを判定できます。

# car_07.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

# 車の状態を表す変数
# 0:車が止まっている, 1:車が動いている
status = 0

def draw():
    screen.clear()
    car.draw()

# 車をマウスでクリックしたときの処理
def on_mouse_down(pos):
    global status
    if car.collidepoint(pos):
        status = 1

def update():
    if status:
        car.y -= 1

pgzrun.go()

あらかじめ、車の状態を表す status という変数を用意してあります。status が 0 なら車が止まった状態、status が 1 なら車が動いている状態です。on_mouse_down(pos) の中で、car.collidepoint(pos) によって、マウスが車のある所をクリックしたかどうかを判定し、当たっていたら status を 1 に変更します。そして、update() 関数内で status が 1 の場合にだけ車を移動させるようにしています。car_07.py を実行して、車がマウスクリックで動き出すことを確認してみてください。

一定時間処理を止める

ゲームでは、スプライトなどに何か生じた時に、少し時間をおいてから反応させたいこともあります。Pygame Zero では、一定時間処理を止める clock.schedule_unique() 関数が用意されています。車をクリックしてから 2 秒後に走り出すコードを書いてみましょう。

# car_08.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(200, 300))

status = 0

def draw():
    screen.clear()
    car.draw()

# 車の状態を変更する関数
def change_status():
    global status
    status = 1

def on_mouse_down(pos):
    if car.collidepoint(pos):
        clock.schedule_unique(change_status, 2)  # (6)

def update():
    if status:
        car.y -= 1

pgzrun.go()

clock.schedule_unique() は第 1 引数に呼び出す関数、第 2 引数に呼び出した関数を実行するまでの時間 (秒) を受け取ります。(6) ではあらかじめ定義しておいた change_status() を 2 秒後に呼び出すように設定しています。change_status() は status 変数を 1 に変更する関数です。

衝突判定

ゲームではスプライト同士が接触したとき、イベントが発生することがよくあります。Pygame Zero では Actor.colliderect() で Actor オブジェクト同士の衝突判定を行なえます。プレイヤーの操作する赤い車とは別に青い車を用意して、衝突した時に ‘CRASH!’ という文字を表示するコードを書いてみましょう。

# car_09.py

import pgzrun

WIDTH = 400
HEIGHT = 400

car_red = Actor('car_red', center=(200, 300))
car_blue = Actor('car_blue', center=(200, 100))

# 車の接触を表す変数
# 0:接触していない, 1:接触している
status = 0

def draw():
    global status
    screen.clear()
    car_red.draw()
    car_blue.draw()
    if status == 1:
        screen.draw.text('CRASH!',
                         (car_red.x, car_red.y),
                         color = 'gray', fontsize = 48)  # (7)
        status = 0

def update():
    global status
    if keyboard.up:
        car_red.angle = 0
        car_red.y -= 2
    elif keyboard.down:
        car_red.angle = 180
        car_red.y += 2
    elif keyboard.left:
        car_red.angle = 90
        car_red.x -= 2
    elif keyboard.right:
        car_red.angle = -90
        car_red.x += 2
    if car_red.colliderect(car_blue):  # (8)
        status = 1

pgzrun.go()

青い車のほうは止まっているので、赤い車を操作して追突して、’CRASH!’ が表示されることを確認してください (なんか煽り運転みたいだけど、現実にはやらないでくださいね)。
 Python 車がクラッシュするゲーム画面
(8) が Actor の衝突を判定しているコードです。ここでは、car_red の colliderect() メソッドの引数に car_blue を渡していますが、もちろん逆でも構いません。衝突していたら status を 1 に変えます。そして、draw() 関数内で、status が 1 の場合に screen.draw.text() でメッセージを表示するようにしてあります。

スプライトを自由な方向へ移動させる

ゲームの種類によっては、スプライトを 4 方向だけでなく、360°自由な向きに移動させたいこともあるでしょう。その場合、三角関数の初歩的な知識が必要です。下図のように、車が反時計周りにθだけ回転したとき、車が向いている方向に距離 1 ピクセルだけ進ませようとする場合、x 方向に – sinθ、y 方向に – cosθ 移動させればよいことになります。
 Pygame Zero 車を角度θの方向へ進ませる計算
次のコードを実行して、車が 30°の方向に移動することを確認してください。

# car_10.py

import pgzrun
import math

WIDTH = 400
HEIGHT = 400

car = Actor('car_red', center=(300, 300))

# 車を反時計回りに30°回転させる
car.angle = 30

# 角度の単位を度数からラジアンに変換
angle_rad = math.radians(car.angle)  # (9)

def draw():
    screen.clear()
    car.draw()

def update():
    car.x -= math.sin(angle_rad)  # (10)
    car.y -= math.cos(angle_rad)  # (11)

pgzrun.go()

Python で三角関数を使用するときは、math モジュールをインポートします。Actor.angle() は度数単位 (degree) の角度を返しますが、math.sin() や math.cos() はラジアン単位 (radian) の角度を受け取るので、(9) のように math.radians() でラジアン単位に変換しておきます。(10) と (11) では、(1/60) 秒毎に x 方向と y 方向に移動させる値を設定しています。

ここまで学んだことを使えば、かなり色々なことができると思います。KENNY のサイトから好きな画像を手に入れて、スプライトを動かしてみてください。とはいえ、ゲームプログラミングに必要な応用テクニックもいくつか覚える必要もあります。ゲーム開始画面とゲーム中画面を切り替えたり、背景画面を表示したり、プレイヤーが操作するスプライトを追尾するような敵の動き方をさせたりすることは、上で学んだ基本事項を組合わせて実現します。今後、少しずつサンプルコードをアップしていく予定なので、時々このサイトに立ち寄ってみてください。それでは、皆さんも Pygame Zero を楽しんでください!

コメント