レッスンの目標

順番に要素を加えながら、ゲームを作って行きましょう。難しいところは先生と一緒にやりましょう!

Screen shot of Lesson 20

ディレクトリ(フォルダ)の移動

ゲームを作成するディレクトリ(フォルダ)に移動します。

cd code/python/introduction

ステップ0 キャラ作成

自分でキャラを作成したい人は、以下に従ってキャラを作成してください。既存のキャラ(ねことリンゴ)をそのまま使う人はスキップしても構いません。

  • プレイヤーのキャラクター(動物)とえさのキャラクター(フルーツ)を16x16の大きさで作成します。
  • 以前あそんだ絵描きツールをちょっと改造したキャラクター作るための専用のツールがあるので、立ち上げましょう
python3 paintchar.py

キャラ作成ツール(paintchar.py)の使い方

上記コマンドでキャラ作成ツール(paintchar.py)を立ち上げると、画面が表示されます。

色の設定

マウスのカーソルは現在選択されている色が表示されています。プログラム立ち上げ時は黒になっています

  • 色を変えたい時は、画面下のカラーパレットのところにマウスのカーソルを移動し、変えたい色のところで左クリックするとその色に変わります。

  • 背景の色は「5」の灰色を使い、この色は背景以外には使わないでください。

色の描画

画面上でマウスを左クリックすると、選択された画素(四角形の箱)が現在の設定色で塗られます。

  • 左クリックを押したまま、マウスを移動すると連続で塗りつぶされます。

色の塗りつぶし

画面上でマウスを右クリックすると、選択された画素(四角形の箱)と同じ色の隣接した領域がまとめて塗りつぶされます。

  • 選択された画素と同じ色の画素が、違う色の色の画素で囲まれていれば、囲まれた内部のみが塗りつぶされます。

キーによる各種機能

キーボードのキーを押すことで、各種機能を実行することができます。

画像の保存(sキー)

sキーを押すと、ファイルダイアログが表示され、ファイル名を入力して「保存」ボタンを押すと、画像が保存されます。ファイル名には「.png」という拡張子が追加され、コマンドラインに「(ファイル名)で保存しました。」というメッセージが表示されます。

キャラクタの作成が終わったらこの操作を行って、好きな名前のファイル名で保存してください。

なお、このコマンドは後述するカット&ペーストモード時は使用できません。

画像の読み込み(oキー)

oキーを押すと、ファイルダイアログが表示され、ファイルを指定して「開く」ボタンを押すと、画像が読み込まれ、コマンドラインに「(ファイル名)を読み込みました。」というメッセージが表示されます。

既存の画像を修正したり、書き加えたりしたい場合は、このコマンドを使用します。

なお、このコマンドは後述するカット&ペーストモード時は使用できません。

画像の初期化(iキー)

画像を初期状態(全て白)に戻したい時はiキーを押します。ただし、この操作はやり直し(undo)ができないので注意してください。

描画のやり直し(undo)(uキー)

描画または後述するカット&ペーストを取り消したい時、uキーを押すと1回につき1回ずつ描画またはカット&ペーストが取り消されます。 ただし、 描画のやり直し(undo)以降に新たな描画またはカット&ペーストを行うと、描画のやり直し(undo)以降の以前の描画またはカット&ペーストに戻ることはできません。

描画のやり直しのやり直し(redo)(rキー)

rキーを押すとuキーで取り消した描画またはカット&ペーストを再度元に戻すことができます。 ただし、 描画のやり直し(undo)以降に新たな描画またはカット&ペーストを行うと、描画のやり直し(undo)以降の以前の描画またはカット&ペーストに戻ることはできません。

カット&ペーストモード移行(pキー)

pキーを押すことでカット&ペーストモードに移行します。

カット&ペーストモードに移行するとマウスのカーソルが描画色から十字に変わります。

カット&ペーストモード時にpキーを押すと通常モードに戻ります。

カット&ペーストモード時は以下の操作が可能になります。

  • 領域の指定(マウス操作)

    指定したい領域の左上にマウスのカーソルを移動し、右クリックを押しながら、指定したい領域の右下までカーソルを移動して右クリックを離すと四角形の線が表示され、領域が指定されたことがわかります。

  • 指定された領域のコピー(cキー)

    領域が指定された状態でcキーを押すと、指定の領域がクリップボード内にコピーされ、コピーされた領域がマウスとともに移動します。

    コピーでは指定された領域の画像はそのままで消えません。

  • 指定された領域のカット(xキー)

    領域が指定された状態でxキーを押すと、指定の領域を削除し、その領域がクリップボード内に移動され、移動された領域がマウスとともに移動します。

    カットでは指定された領域の画像自体は消えてしまいます。

  • クリップボードの画像のペースト(マウス操作)

    領域がコピーまたはカットされた状態でマウスのカーソルを移動するとクリップボードの画像が移動するので、ペーストしたい位置で右クリックすると、クリップボードの画像がその位置に描画されます。

    描画後もクリップボードの画像は消去されませんので、何回も同じ画像をペーストすることができます。

    ペーストの操作は前述のundo操作(uキー)で取り消すことができます。

ステップ1 最初のプログラムを作成する

プレイヤーの画像を表示するだけのゲームを作ります。あまり面白くないですが、大事なスタートです。

プログラムの内容は下記の通りです。game.pyという名前で作成します。1から自分で打ち込んでもいいですし、下記のプログラムをコピぺしても構いません。

# 使用するライブラリを呼び出します
import sys
import pygame
from pygame.locals import *

# pygameの初期設定を行います
pygame.init()
SURFACE = pygame.display.set_mode((800, 600))	#Windowサイズは800x600ピクセル
FPSCLOCK = pygame.time.Clock()
TICK = 15	# 描画間隔は1秒間に15コマ

# メインルーチン
def main():
	# プレイヤー(猫)のイメージファイルを読み込み、5倍(80x80)に拡大する
	image = pygame.image.load("cat.png")
	player_image = pygame.transform.scale(image, (80, 80))
	# 描画処理
	while True:	# メインループ
		for event in pygame.event.get():	# 各種イベントを取得
			if event.type == QUIT:	# 右上の×ボタンをクリックしたら終了
				pygame.quit()
				sys.exit()
		SURFACE.fill((0, 0, 0))	# 画面全体を黒で塗りつぶす
		SURFACE.blit(player_image, (375, 225))	# プレイヤー(猫)を描画
		pygame.display.update()	# 画面を更新
		FPSCLOCK.tick(TICK)		# 描画間隔を調整
	
if __name__ == '__main__':	# ここからプログラムスタート
	main()	#メインルーチンを呼び出す

キャラを作成した人は、“cat.png"の部分をステップ0で自分が作成した動物に変えてみてください。

game.pyの編集が終わったら、保存して、次のコマンドで実行してみましょう

python3 game.py

プレイヤーが灰色の背景とともに表示されます。

ステップ2 プレイヤーの背景をなくす

プレイヤー(猫)の背景(灰色の部分)を透過します。プレイヤー(猫)の画像の背景色は灰色なので透過色に灰色(RGB(128,128,128))を指定します。

	player_image = pygame.transform.scale(image, (80, 80))

と書いてある行のあとに、次の一行を追加してください。

	player_image.set_colorkey((128,128,128)) # 灰色(RGB(128,128,128))を透過色にする

その一行の追加ができたら、保存して、またこのコマンドで実行してみましょう

python3 game.py

プレイヤーの背景がなくなります。

ステップ3 プレイヤーを動かす

プレイヤー(猫)を動かします。キーボード操作で上下左右に動かします。

3.1

SURFACE = pygame.display.set_mode((800, 600)) #Windowサイズは800x600ピクセル

の行のあとに、次の一行を追加してください。

pygame.key.set_repeat(15, 15) # キーを押し続けても一定間隔でイベントを発生させる

3.2

	player_image.set_colorkey((128,128,128)) # 灰色(RGB(128,128,128))を透過色にする

の行のあとに、次の行を追加してください

	# プレイヤー(猫)の位置を変数に設定する
	player_x = 375
	player_y = 225

3.3

				sys.exit()

の行のあとに、次の行を追加してください

			if event.type == KEYDOWN:	# キーが押されたら
				if event.key == K_LEFT:	# ←キーなら
					player_x = max(player_x - 10, 0)	# 0以下にはならない
				if event.key == K_RIGHT:	# →キーなら
					player_x = min(player_x + 10, 720)	# 720以上にはならない
				if event.key == K_UP:	# ↑キーなら
					player_y = max(player_y - 10, 0)	# 0以下にはならない
				if event.key == K_DOWN:	# ↓キーなら
					player_y = min(player_y + 10, 520)	# 520以上にはならない				

3.4

		SURFACE.blit(player_image, (375, 225))

の行を次の一行に変えてください

		SURFACE.blit(player_image, (player_x, player_y))

ここまで入力し保存したら、実行してみましょう。

python3 game.py

矢印キーでプレイヤーが動きます。

ステップ4 えさのフルーツを出現させる

えさのフルーツ(リンゴ)を出現させてみます。一定間隔でランダムな位置(高さ)に出現させ、一定の速度で移動させます。

4.1

5行目に次のインポート文を追加します。

from random import randint # ランダムな値を発生させる

4.2

def main():

の行のあとに次の行を追加します。

	image = pygame.image.load("apple.png")
	fruit_image = pygame.transform.scale(image, (80, 80))
	fruit_image.set_colorkey((128,128,128)) # 灰色(RGB(128,128,128))を透過色にする
	# えさのフルーツ(リンゴ)を一定間隔で4個、ランダムな高さに出現させる。(x, y, 有効)
	fruits = [[i * 300, randint(0, 520), True] for i in range(4)]

“apple.png"のところは自分が作ったえさの絵のファイル名を指定してください。

4.3

		SURFACE.fill((0, 0, 0)) # 画面全体を黒で塗りつぶす

の行を消して、代わりにこの行を追加します。

    	SURFACE.fill((0, 191, 255))     # 画面全体を淡い青で塗りつぶす
    	for fruit in fruits:
        	if fruit[2] and fruit[0] < 800: # フルーツが有効で描画範囲内なら指定の位置に描画する                                                  
    			SURFACE.blit(fruit_image, (fruit[0], fruit[1]))

4.4

		FPSCLOCK.tick(TICK)

の行のあとに次の行を追加します。

		for fruit in fruits:
			fruit[0] -= 20  # 各フルーツを左に20移動                
			if fruit[0] < -40:      # フルーツが左端を超えそうなら右端奥に移動し、高さはランダムに再設定                                                
				fruit[0] += 1200
				fruit[1] = randint(0, 520)
				fruit[2] = True # 有効にする

ここまで入力し保存したら、実行してみましょう。

python3 game.py

えさのフルーツが表示されます。

ステップ5 プレイヤーがえさのフルーツを食べる

プレイヤー(猫)がえさのフルーツ(リンゴ)を食べます。プレイヤー(猫)がえさのフルーツ(リンゴ)に一定距離近づいたら、えさのフルーツを表示しないようにします。

		for fruit in fruits:

のあとに次に行を足してください

			# 命中判定: フルーツが有効で、フルーツの位置(左上)がプレイヤーの位置(左上)の上下左右±60以内なら                                  
			if fruit[2] and abs(fruit[0] - player_x) < 60 and abs(fruit[1] - player_y) < 60:
				fruit[2] = False        # フルーツを無効(食べた)にする

ここまで入力し保存したら、実行してみましょう。

python3 game.py

えさに近づいたら、えさが消えます。

ステップ6 得点を表示する

得点を表示します。得点を表示し、プレイヤー(猫)がえさのフルーツ(リンゴ)を食べたら得点を100点追加します。

6.1

	fruits = [[i * 300, randint(0, 520), True] for i in range(4)]

のあとに次の行を足します。

	# 点数を表示するフォント(文字種)と大きさを設定する
	scorefont = pygame.font.Font("ipaexg.ttf", 24)
	# 点数
	score = 0

6.2

		SURFACE.fill((0, 191, 255)) # 画面全体を淡い青で塗りつぶす

のあとに次の行を足します。

		score_image = scorefont.render("Score {:>4}".format(score), True, (255,255,255))
		SURFACE.blit(score_image, (20,20))

6.3

				fruit[2] = False # フルーツを無効(食べた)にする

のあとに次の行を足します。

				score += 100 #100点追加

ここまで入力し保存したら、実行してみましょう。

python3 game.py

えさをとると点数が増えます。

次に、課題をやってみましょう。ここからは自分で考えないといけません。

課題1

えさのフルーツの速度を最初はゆっくり、だんだん早くするようにしてみてください。

ヒント

  • 「# 各フルーツを左に20移動」という部分の-20という値を変更すると速度が変わります
  • 時間によって速度を変えたければ、ループのカウンタを作って、カウンタの値によって速度を変化させます
  • 得点によって速度を変えたければ、得点の値によって速度を変化させます

課題2

プレイヤー(猫)は常に左向きですが、動く方向によって向きを変えてください。

ヒント プレイヤーの左右の向きを変えるのは以下の命令です。TrueとFalseを逆にすると上下の向きが変わります。

player_image = pygame.transform.flip(player_image, True, False)

上下左右のキーを押したとき、現在の向きと異なる場合、向きを変える必要があります。現在の向きを覚えておいて、適切に向きを変えるようにしましょう。まずは、左右だけやってみてください

課題3

えさの大きさを、ランダムに大きくしたり、小さくしたりして、命中の難易度を変え、大きさによって得点が変わる(大きいほど点数が低く、小さいほど点数が高い)ようにしてください。

ヒント

  • えさの情報は現在[x位置、y位置、有効か]の3つですが、これに大きさを追加します。
  • 大きさは現在(80,80)ですが、これをy位置をランダムにしたように、例えばrandint(40,160)で1/2~2倍の範囲に変更します。
  • 変更したら描画のときに変更したsizeを以下の命令を行えば、変更できます。 元のイメージを書き換えるのではなく、描画直前に変更して変更したものを描画するようにしましょう。
fruit_image = pygame.image.load("apple.png")
[...]
image = pygame.transform.scale(fruit_image, (size, size))
SURFACE.blit(image, (fruit[0], fruit[1]))
  • ただし、大きさを変更してもこのままでは命中の難易度が変わらないので、命中判定の部分も変える必要があります。次の課題で行います。
  • 得点も大きさに合わせて変更する必要があります。(大きいほど得点が低くなる)

課題4

プレイヤーとえさの当たり判定が「フルーツの位置(左上)がプレイヤーの位置(左上)の上下左右±60以内」となっているため、フルーツやプレイヤーの画像が四角に近い場合は、重なっていても当たりと判定されず、すり抜ける場合があります。

また、フルーツのサイズを変えた場合、大きいときは当たっていてもすり抜け、小さいときは当たっていなくても当たりとなる場合があります。

プレイヤーとフルーツの画像を四角形と仮定した場合と円形と仮定した場合のどちらかを採用して、フルーツのサイズが変わっても当たり判定をより正確にするようにしてください。

ヒント

  • 四角形と仮定した場合

    画像同士が重なり合うのは、以下の4つの条件がすべて満たされた場合

    • プレイヤーのx座標が(えさのx座標+えさの大きさ)以下
    • えさのx座標が(プレイヤーのx座標+プレイヤーの大きさ(80))以下
    • プレイヤーのy座標が(えさのy座標+えさの大きさ)以下
    • えさのy座標が(プレイヤーのy座標+プレイヤーの大きさ(80))以下
  • 円形と仮定した場合

    • プレイヤーの半径(pr)はプレイヤーのサイズ ÷ 2
    • えさの半径(fr)はえさのサイズ ÷ 2
    • プレイヤーの中心x座標(px)は(プレーヤーのx座標 + pr)
    • プレイヤーの中心y座標(py)は(プレーヤーのy座標 + pr)
    • えさの中心x座標(fx)は(えさのx座標 + fr)
    • えさの中心y座標(fy)は(えさのy座標 + fr)

    画像同士が重なり合うのは、上記に対し、(px - fx)** 2 + (py - fy) ** 2 が(pr + fr)** 2 以下の時

    • 注1 ** 2 とはpythonで二乗(同じものを2回かける)を意味します
    • 注2 この方法では、プレイヤーの中心の位置とえさの中心の位置との距離とプレイヤーの半径とえさの半径を足した長さを比較しています。2点間の距離の求め方はピタゴラスの定理(中3の数学で習います)を使い√(dx^2+dy^2)となり(dx, dyはそれぞれ2点間のxの距離とyの距離)、これをpr+drと比較しています。(実際は√を使わず、互いに二乗しています)

課題5

えさを敵として、プレイヤーにぶつかるとゲームオーバーになるように変更してみましょう。

ヒント

  • えさの画像を敵の画像に変更します。

  • isGameOverという変数を作成し、初期値はFalseにし、えさとプレイヤーが接触したらscoreを加算する代わりにこの変数をTrueにします。

  • scoreを削除し、scorefontをgameOverfontに変更し、フォントの大きさを100にします。

  • gameOver_image = gameOverfont.render("GameOver", True, (255,0,0))で赤文字のゲームオーバーを設定しておきます。

  • scoreの表示部分を削除し、 isGameOverがTrueなら、プレイヤーを表示するかわりに、gameOver_imageを表示するように変更します。

  • いきなりぶつからないように、以下のようにえさ(敵)の最初のx座標を+800します。

    fruits = [[i * 300 + 800, randint(0, 520), True] for i in range(4)]

課題6

プレイヤーが逃げてばかりではつまらないので、スペースキーを押すとプレイヤーから弾丸が発射され、弾丸があたると敵が消えるように変更してみましょう。

ヒント

  • shotsという変数を作成し[](空の配列)で初期化します。
  • if event.key == K_SPACE :という条件を追加し、この条件になったらshots.append([player_x+40, player_y+40, <向き>])を実行し、弾丸を追加します。<向き>の部分には課題2で追加したプレイヤーの向きを示す変数を設定します。なお、弾丸を打ちやすいように、最初の向きは右向きに変更しておきましょう。
  • えさを描画している部分に
for shot in shots:
	if shot[0] >= 0 and shot[0] < 800:
		pygame.draw.ellipse(SURFACE, (255,255,255), Rect(shot[0]-5, shot[1]-5, 10, 10))

を追加します。 ここで、5, 10というのは弾丸の半径、直径になります。また(255,255,255)は弾丸の色(ここでは白)になります。

  • えさを移動している部分に
for shot in shots:
	if shot[2] == <>:
		shot[0] -= 40
	else:
		shot[0] += 40

を追加し、弾丸を移動します。40は弾丸のスピードになります。

  • えさのプレイヤーとの命中判定の部分に、以下のえさと弾丸との命中判定も追加します。

    課題4の円形の衝突判定を採用します(四角形の衝突判定を採用しても構いません) プレイヤーと敵、弾丸と敵の両方の衝突判定を行えるようcollisionという関数を作成します。

def collision(x, y, size, fruit):
	pr = size / 2
	fr = fruit[3] / 2
	px = x + pr
	py = y + pr
	fx = fruit[0] + fr
	fy = fruit[1] + fr
	return (px - fx) ** 2 + (py - fy) ** 2 <= (pr + fr) ** 2

 敵とプレイヤーの衝突判定に加え、敵と弾丸の衝突判定も追加します。  

	for fruit in fruits:
		for shot in shots:
			if fruit[2] and collision(shot[0], shot[1], 10, fruit):
				fruit[2] = False
				shot[0] = -1000
				score += 100
		if fruit[2] and collision(player_x, player_y, 80, fruit):
			fruit[2] = False
			isGameOver = True

 命中したら弾丸が消えるよう、x座標を-1000にしておきます。  また、弾丸に命中したら得点を加算するように変更します。

  • 最後に、表示範囲外になった弾丸をリストから削除します。
shots = [shot for shot in shots if shot[0] >= 0 and shot[0] < 800]

範囲内の弾丸だけを残す処理です。

課題7

えさ(敵)をなくし、代わりにプレイヤーをもう一人増やして、プレイヤー同士が弾丸を打ち合い、当てた方が勝ち(当たった方が負け)にするゲームを作ってみましょう。

変更箇所が多いので、ファイル名を変えて新しいゲームとして作成しましょう。

ヒント

  • 移動、弾丸の発射は2人でプレイできるよう以下のキーに変更します
    • プレイヤー1(左側)
      • 上移動: wキー
      • 下移動: xキー
      • 弾発射: sキー
    • プレイヤー2(右側)
      • 上移動: uキー
      • 下移動: mキー
      • 弾発射: jキー
  • プレイヤー1は左端(0, 225)、プレーヤー2は右端(720, 225)に配置します。
  • プレイヤー1は右向き、プレイヤー2は左向きとします。
  • プレイヤーは上下にしか動けず、向きも変わりません。
  • 弾丸の向きでどちらのプレイヤーが打った弾丸か判断できますので、自分の打った弾丸ではやられないようにします。