OpenCV

ブロック崩しを半日で作る4

今回がこのシリーズ最後の記事となります。(前回の記事はこちら

ボールとバー、壁、ブロックの接触(4,5,6)とゲームクリア(8)の機能を実装していきます。

ボールとバーの接触です。最初のif文でボールがバーと接触していることを確認して、2個目のif文でバーの右側に当たるか、左側に当たるかを判定して反射の方向と速度を変えています。ただし、vector_x<=3,vector_x>=-3の部分で x軸方向のスピードが速くなりすぎないように調整しています。

def ball_bar():
	global bar_x, ball_x, ball_y, vector_x, vector_y
	
	if ball_y>588 and bar_x<=ball_x and ball_x<=bar_x+72:
		if ball_x<=bar_x+36 and vector_x<=3:
			vector_x = vector_x + 1
		elif vector_x>=-3:
			vector_x = vector_x - 1
		vector_y = -vector_y

続いて、ボールと壁の接触です。
こちらはコメントしなくても大丈夫だと思います。

def ball_wall():
	global ball_x, ball_y, vector_x, vector_y, cnt

	if ball_y<8:
		vector_x = vector_x + 1
		vector_y = -vector_y

	elif ball_x<5 or ball_x>480:
		vector_x = -vector_x
		vector_y = vector_y

ボールとブロックの接触です。これは少し考えないといけません。
というのもフィールド上にはブロックは複数あり、ボールが当たったブロックだけ消さないといけないからです。

ここでは素直な考え方をしてみましょう。つまり、ひとつひとつのブロックがそれぞれボールに当たったか判定していけばいいのです。そのためには、まずinitialize()の中身を少し変更して、それぞれのブロックをタグ付けしていく必要があります。

def initialize(wallpaper, bar, ball, block):
	global ball_x, ball_y, bar_x, bar_y, vector_x, vector_y, block_x, block_y, cnt
	ball_x = 246
	ball_y = 500
	bar_x = 214
	vector_x = 0
	vector_y = 3
	block_x = []
	block_y = []
	cnt = 0

	for i in range(20, 406, 77):
		for j in range(5, 100, 17):
			wallpaper[j:j+block.shape[0], i:i+block.shape[1]] = block
			block_x.append(i)
			block_y.append(j)
			cnt=cnt+1

	wallpaper[ball_y:ball_y+ball.shape[0], ball_x:ball_x+ball.shape[1]] = ball
	wallpaper[600:600+bar.shape[0], bar_x:bar_x+bar.shape[1]] = bar

	cv2.imshow('Breakout', wallpaper)
	cv2.waitKey(0)

変更点はblock_x, block_y, cntの各部位です。
block_x, block_yは配列として、引数に応じたブロックの座標をappendを使って入れていってます。ちなみに、このblock_x, block_yの引数こそが、各ブロックのタグということになります。
なお、cntはそのままブロックの数を表しています。こうすることでcntが0になればゲームクリアにすればいいことが感覚的にもわかりますよね。

さて、肝心のボールとブロックの接触に関する関数について考えます。
関数というのは基本的に1つの役割しか持たせるべきではない、というのが原則です。
なので、この関数では、すべてのブロックに対する判定を行うのではなく、ある1つのブロックがボールに当たったかどうかを判定するないようにします。

def ball_block(i):
	global ball_x, ball_y, vector_x, vector_y, block_x, block_y

	if block_y[i]<=ball_y and ball_y<=block_y[i]+12 and block_x[i]<=ball_x and ball_x<=block_x[i]+72:
		vector_y = -vector_y
		wallpaper[block_y[i]:block_y[i]+block.shape[0], block_x[i]:block_x[i]+block.shape[1]] = wallpaper_ori[block_y[i]:block_y[i]+block.shape[0], block_x[i]:block_x[i]+block.shape[1]]

関数の引数としてiを受け取っています。これが先ほどのブロックのタグというわけです。そして、block_x, block_yに入っている、そのブロックの座標をもとにボールと接触していないかどうかを調べているわけです。あっさりしてますよね。
最後にゲームクリアについて考えましょう。

for n in range(cnt-i-1):
        block_x[i+n]=block_x[i+n+1]
        block_y[i+n]=block_y[i+n+1]
	cnt=cnt-1

	if cnt == 0:
		print("Clear!")
		exit()

さて、ここまで接触にかかわる関数を3つ書いてきました。
これらをメインのコードに統合すれば、いよいよ完成です。
統合する場所はmove_ball()関数の中にします。

def move_ball(): #game over is also set here.
	global ball_x, ball_y, vector_xs, vector_y, cnt
	wallpaper[ball_y:ball_y+ball.shape[0], ball_x:ball_x+ball.shape[1]] = wallpaper_ori[ball_y:ball_y+ball.shape[0], ball_x:ball_x+ball.shape[1]]
	ball_x = ball_x + vector_x
	ball_y = ball_y + vector_y
	wallpaper[ball_y:ball_y+ball.shape[0], ball_x:ball_x+ball.shape[1]] = ball
	cv2.imshow('Breakout', wallpaper)

	if ball_y == 620:
		print("GAME OVER")
		exit()

	ball_bar()
	ball_wall()

	for i in range(cnt):
		ball_block(i)


ここに入れることによって、ボールが少し動くたびに接触判定を行うことができます。「ボールが動くたびに接触判定してたら、計算量が膨大になって動きがカクカクするんじゃないの?」と思われるかもしれないですが、これくらいであれば、問題なくスムーズにゲームを楽しめるはずです。(PCのスペックによって状況が異なるかもしれませんが...)
また、ball_block()に注目すると、for文によって全てのブロックについて判定していることがわかるとおもいます。


おつかれさまでした。最終的なコードはこちらになります。
やってみていかがでしたか?
実のところ、このコードには改善すべき点がいっぱいあります。
それは、計算量や使用するメモリの削減、小さなバグの除去、新しい機能の追加などです。これらについては、ぜひご自身で取り組んでみてください。
そして、完成したオリジナルなコードはGitHubに投稿することで、ほかの人の作品と比べてみたり、自分のポートフォリオを増やす楽しみも味わえば、またコードを書いてみようという気も起きるはずです。

関連記事

  1. OpenCV

    ブロック崩しを半日で作る1

    はじめにPythonの学習を始めてから2週間~1か月程度の方…

  2. OpenCV

    ブロック崩しを半日で作る3

    3回目の記事になりました。(前回の記事はこちら)今回は、ボールとバ…

  3. OpenCV

    ブロック崩しを半日で作る2

    ブロック崩しを半日で作る2です。まずはおさらいとして、前回考えた実装…

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

カテゴリー

最近の記事

おすすめ記事

  1. その他

    CS系の海外研究インターン情報
  2. Environment

    CUDAインストール時の注意点
  3. その他

    トビタテのインターンの見つけ方
  4. Environment

    個人的にLinuxでよく使うコマンドリスト
  5. その他

    Panasonic Singaporeでのインターンの思い出2
PAGE TOP