R-1グランプリで優勝。「デッカチャン」ゲームの作り方解説
今回はこれです。
R-1ぐらんぷり2020優勝、野田クリスタルさんが開発した
「ブロックくずして」というゲームについて書いていきます。
デッカチャンを動かしてブロック崩しのボールを避けるという
常人には到底思い浮かばないこのゲームを今回は再現してみます。
いきなりこの狂気に満ち溢れるゲームを作るのは大変なので、
まずは、基礎となるブロック崩しがどのように作成さてているか
理解しましょう。
ここからの内容は「そのままのJavaScriptを使ったブロックくずしゲーム」
をpythonのtkinterで書き直したものとなります。
クラス定義等の難しい概念は使われていないので、
プログラミング初心者の方でも内容を理解しやすいと思います。
それでは作成していきます。
1.デッカチャンゲーム画面の作成
まずはゲーム画面を作りましょう。
ゲーム画面は次のコードで作成できます。
1 2 3 4 5 6 7 8 9 10 11 12 |
#ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #ウィンドウのタイトルとサイズを設定 root.title("サンプルゲーム") root.minsize(480, 320) #ウィンドウの表示とメインループ root.mainloop() |
ウインドウを作成する基本のテンプレートです。9行目を変更することで
ウインドウのサイズを変更することができます。
このコードを実行すると下の図ようになります。空白のゲーム画面ができました。
2.図形を書くためのキャンバスを作成する
ゲーム画面が作成できましたので、次はボールを表示していきましょう。
ゲーム画面にボールを表示するためには「キャンバス」という部品をウインドウに
追加します。
キャンバスの上には画像や、四角、丸などの図形を書くことができます。
今回はウインドウサイズと同じ大きさで白のキャンバスを設定しました。(15-17行目)
そして中心あたりに円を作成しています。(19-20行目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#===================== # 円を表示する #===================== #ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(480, 320) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=480, height=320) canvas.place(x=-3,y=-3) #円を表示 canvas.create_oval(235, 155, 255, 175, fill="#4286f4",outline="") #ウィンドウの表示とメインループ root.mainloop() |
実行すると次の様になります。
crate_ovalを使うことで円を書くことができます。
ウインドウには左上を0として、x軸とy軸が設定されています。
xとyを指定することでウィンドウの1点の座標を表すことができます。
create_ovalは2つの座標から得られる四角形の内円を描きます。
図にすると次の様なイメージです。
これで画面上にボールを書くことができました。
次はこのボールを動かしていきましょう。
3.デッカチャンゲームの作り方|パラパラ漫画方式でボールを動かす
ゲーム作成において、物体を動かす場合、パラパラ漫画の要領で動かしたい物を
少しづつずらしていきます。
実際のプログラムは次の様に書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#================================ # ボールを動かす #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(480, 320) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=480, height=320) canvas.place(x=-3, y=-3) x = 5 y = 5 #繰り返し処理の定義 def game_loop(): global x, y canvas.create_oval( 235 + x, 155 + y, 255 + x, 175 + y, fill="#4286f4", outline="") x = x + 5 y = y - 5 root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
やや難しくなってきましたが、ポイントは2つです。
1. create_ovalで円の座標をずらしていく。(25-28行目)
2.root.after(50, game_loop)という部分でgame_loop()関数を定期的に呼び出す(29行目)
1の処理で円を右上に少しずらして描写することができますが、描写を一度しか実行しない場合は
ちょっと動いて止まったままになってしまいます。
そこで、ボールをずらすための処理を定期的に繰り返すために、afterという関数を用いて、
50ミリセカンドごとにgome_loop関数を呼び出しています。
この処理によって、繰り返しボールを描写し、動いている様に見せることができます。
実行してみましょう
・・・・・・・なんか違います。
このようになってしまう原因はボールを描写した軌道が次の描写時に
残ったままになってしまうからです。
円を表示する度に過去の円を消すようにしましょう。(26-27行目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#================================ # 既存の起動を消す #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(480, 320) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=480, height=320) canvas.place(x=-3, y=-3) x = 5 y = 5 #繰り返し処理の定義 def game_loop(): global x, y #消す canvas.delete("all") #再描写 canvas.create_oval( 235 + x, 155 + y, 255 + x, 175 + y, fill="#4286f4", outline="") x = x + 5 y = y - 5 root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
それでは実行結果を確認しましょう!
無事に左上に向かって動いていく様子が確認できたと思います。
ここからゲームに必要な要素を組み込んでいくのですが、このままだとコードは
複雑になってしまいますので一旦機能ごとに整理しましょう。
4.デッカチャンゲームの作り方|機能ごとに処理を分ける
前回のプログラムの内容は、円を描く機能と画面の更新機能を同時に
書いてしまったため、すこし見辛いコードになっています。
今回は、コードの見通しをよくするためと、今後の機能の追加がしやすい様に
それぞれを関数に分ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#================================ # コードを整える #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #座標保存用変数 PointOneX = 135 PointOneY = 155 #移動差分 dx = 5 dy = 5 #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): global PointTwoX global PointTwoY PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval( PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4", outline="") #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY #canvasを全て消す canvas.delete("all") drawBall() PointOneX = PointOneX + dx PointOneY = PointOneY + dy root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
drawBall関数(35-42行目)とgame_loop関数(45-56行目)でそれぞれの機能を分ました。
また、円の直径をcircleWidth(16行目)、
移動する距離をdx, dy(18-20行目)とすることで円のサイズやボールのスピードの変更
に柔軟に対応できる様に変更しました。次はボールを壁に反射させてみましょう。
5.ボールを壁で反射させる
ボールを反射させる方法はシンプルです。
左右の壁にボールが衝突した場合はx軸の進む方向を反転させます。
上下の壁にボールが衝突した場合はy軸の進む方向を反転させます。
もし斜めに当たってもこの法則は変わりません
こんな感じです。
まとめると、上下の壁に衝突した場合はy方向に-1を掛ける、左右の壁に衝突した場合は
x方向に-1をかければOKです。(57-77行目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
#================================ # 反射させる #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #座標保存用変数 PointOneX = 135 PointOneY = 155 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): global PointTwoX, PointTwoY PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval( PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4", outline="") #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global dx, dy #中点を求める centerX = PointOneX + (circleWidth / 2) centerY = PointOneY + (circleWidth / 2) #右にボールが抜けそうな場合反射させる print(PointTwoX) if PointTwoX > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる #if centerX + dx < 0: if PointOneX < 0: dx = -dx #下にボールが抜けそうな場合反射させる if PointTwoY > HEIGHT: dy = -dy #上にボールが抜けそうな場合反射させる if PointOneY < 0: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
実行してボールがウインドウ内で跳ね返ることが確認できればOKです!
次はボールを跳ね返すパドルを作成します。
6.デッカチャンゲームの作り方|ボールを跳ね返すパドルを作成する。
ボールの反射ができたので、次はボールを跳ね返す板、「パドル」を作成します。
パドルの形は単純な四角形で、create_rectangle関数で2点の座標を通る四角形を描写する
ことができます。内円を書くときに描く四角形がそのまま表示されるということですね。
四角を書くために必要な変数を定義している部分が(32-36行目)
実際にパドルとなる四角を表示する部分が(56-66行目)となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
#================================ # パドル追加 #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt(2 * ((circleWidth / 2)**2)) #座標保存用変数 PointOneX = 135 PointOneY = 155 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): global PointTwoX, PointTwoY PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval( PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4", outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle( paddleX, paddleY, puddleBottomeRightX, puddleBottomeRightY, fill='#4286f4', outline="") #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global dx, dy #中点を求める centerX = PointOneX + (circleWidth / 2) centerY = PointOneY + (circleWidth / 2) #右にボールが抜けそうな場合反射させる print(PointTwoX) if PointTwoX > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる #if centerX + dx < 0: if PointOneX < 0: dx = -dx #下にボールが抜けそうな場合反射させる if PointTwoY > HEIGHT: dy = -dy #上にボールが抜けそうな場合反射させる if PointOneY < 0: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
パドルが無事に表示されました!
しかし、これでは何も動かないので、
パドルを動かす処理を書いていきましょう。
7.パドルを動かす
それでは先ほど書いたパドルを動かしていきます。
考え方はボールを動かしたときと同じです。
少しずつ動かしながら画面を切り替えれば、
パラパラ漫画のように動き出します。
ボールの時と違うのは
「キーを押した時にパドルを動かす」
ということです。
pythonではキーを押した時と離した時に特定の
関数を呼び出すことができます。これを利用して、
左矢印キーと右矢印キーが押された場合のみ
座標を変更できる様にします。
もちろん画面外にパドルが出ていってしまうことを防ぐため、
if文で画面の幅からはみ出す場合は、移動する処理を無効にする
制限を持たせます。
実際のプログラムでは以下のようになります。
左右のボタンを押したかどうかの判断結果を保存する変数の定義(38-41行目)
キーを押したときに呼び出される関数(63-81行目)
左右のキーに関数を割り当てる処理(83-87行目)
パドルの移動制限(98-106行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
#================================ # パドル操作追加 #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt( 2*((circleWidth/2)**2)) #座標保存用変数 PointOneX = 135 PointOneY = 155 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 rightPressed = False leftPressed = False paddleSpeed = 10 #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval(PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4",outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle(paddleX, paddleY, puddleBottomeRightX,puddleBottomeRightY, fill = '#4286f4', outline="") #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX #右方向のパドルの動きを制限する if rightPressed: if paddleX + paddleWidth +paddleSpeed < WIDTH: paddleX = paddleX + paddleSpeed #左方向のパドルの動きを制限する if leftPressed: if paddleX - paddleSpeed > 0: paddleX = paddleX - paddleSpeed #中点を求める centerX = PointOneX + (circleWidth/2) centerY = PointOneY + (circleWidth/2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: dy = -dy #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
次はゲームオーバーの作成です。
8.デッカチャンゲーム|ゲームオーバーの作成
通常のブロック崩しゲームはパドルでボールを捉えたときは反射させ、
受け止められなかったときにゲームオーバになります。
この機能は「ブロックくずして」には必要ないのですが、
確認のため、ゲームオーバーの機能をつけておきましょう。
内容は単純です。先ほどまで下の壁に当たった場合は反射させていましたが、
その部分をゲームオーバーに変更するだけです。
次はパドルの上にボタンが衝突した際の反射処理ですが、少し複雑です。
ボールの移動先にパドルが存在する場合はy方向の動きを反転すればOKなのですが、
パソコンは座標でしか判定できないため、座標でボールの位置とパドルの位置関係を
精査してあげる必要があります。(121-131行目)
この方法は後の壊されるブロックでも同じことをするので覚えておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
#================================ # パドルで跳ね返ってくる処理 #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt( 2*((circleWidth/2)**2)) #座標保存用変数 PointOneX = 135 PointOneY = 155 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 rightPressed = False leftPressed = False paddleSpeed = 10 #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval(PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4",outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle(paddleX, paddleY, puddleBottomeRightX,puddleBottomeRightY, fill = '#4286f4', outline="") #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX #右方向のパドルの動きを制限する if rightPressed: if paddleX + paddleWidth +paddleSpeed < WIDTH: paddleX = paddleX + paddleSpeed #左方向のパドルの動きを制限する if leftPressed: if paddleX - paddleSpeed > 0: paddleX = paddleX - paddleSpeed #中点を求める centerX = PointOneX + (circleWidth/2) centerY = PointOneY + (circleWidth/2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: #ボールの移動先がパドル状の場合は跳ね返す if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: dy = -dy #ボールの移動先にパドルがない場合はゲームオーバーとする else: canvas.delete("all") canvas.create_text(WIDTH/2,HEIGHT/2,font=("",25), fill="black",text="ゲームオーバー") return #dy = -dy #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
9.ブロックの作成
続いてブロック崩しのブロックを作成します。
ブロックはcreate_rectangleを使用して画面に表示していきますが、
数が多いため、リストを利用します。
各ブロックの座標をディクショナリの型で定義し、
繰り返し処理でブロックを表示していきます。
プログラムでは下記の位置にコードを追加しています。
ブロックを作成するのに利用する変数の定義(43-60行目)
繰り返し処理でブロックを画面上に表示する処理(92-109行目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
#================================ # 壊せるブロックを作成する #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt(2 * ((circleWidth / 2)**2)) #座標保存用変数 PointOneX = 135 PointOneY = 155 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 rightPressed = False leftPressed = False paddleSpeed = 10 #破壊ブロックに関する変数 blockstartX = 20 blockstrtY = 20 blockRowCount = 3 blockColumnCount = 5 blockWidh = 75 blockHeight = 20 blockPadding = 10 blockOffsetTop = 15 blockOffsetLeft = 15 arrBlock = [] #リスト作成 for r in range(blockRowCount): tmpBlock = [] for c in range(blockColumnCount): tmpBlock.append({"x1": 0, "y1": 0, "x2": 0, "y2": 0}) arrBlock.append(tmpBlock) #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval( PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4", outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle( paddleX, paddleY, puddleBottomeRightX, puddleBottomeRightY, fill='#4286f4', outline="") def drawBlock(): for r in range(blockRowCount): for c in range(blockColumnCount): leftXposition = blockstartX + (c * blockWidh) + ( c * blockOffsetLeft) leftYposition = blockstrtY + (r * blockHeight) + ( r * blockOffsetTop) rightXposition = leftXposition + blockWidh rightYposition = leftYposition + blockHeight #canvas.create_rectangle(10, 10, 10+10+blockWidh*c,10+10+blockHeight*r, fill = '#4286f4', outline="") canvas.create_rectangle( leftXposition, leftYposition, rightXposition, rightYposition, fill='#4286f4', outline="") #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX #右方向のパドルの動きを制限する if rightPressed: if paddleX + paddleWidth + paddleSpeed < WIDTH: paddleX = paddleX + paddleSpeed #左方向のパドルの動きを制限する if leftPressed: if paddleX - paddleSpeed > 0: paddleX = paddleX - paddleSpeed #中点を求める centerX = PointOneX + (circleWidth / 2) centerY = PointOneY + (circleWidth / 2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: #ボールの移動先がパドル状の場合は跳ね返す if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: dy = -dy #ボールの移動先にパドルがない場合はゲームオーバーとする else: canvas.delete("all") canvas.create_text( WIDTH / 2, HEIGHT / 2, font=("", 25), fill="black", text="ゲームオーバー") return #dy = -dy #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() drawBlock() root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
実行してみると次のようになります。
ボールがブロックを貫通していきます。
上で追加した処理はブロックを表示しているだけなので、当然です!
次はブロックにぶつかったらブロックを消して反射させてみましょう。
10.ブロックとボールが衝突したときの処理
ボールがブロックと衝突して反射する処理はボールがパドルに衝突した時の処理と同じです。
パドルとブロックで異なる部分は、ブロックはボールがぶつかったら「消える」ということです。
「消える」ということは、「非表示にする」ということで対応できるため、ボールがブロックと
衝突した場合は非表示にできる様にプログラムを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
#================================ # ブロックに当たったらブロックを消す #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt(2 * ((circleWidth / 2)**2)) #座標保存用変数 PointOneX = 235 PointOneY = 255 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 rightPressed = False leftPressed = False paddleSpeed = 10 #破壊ブロックに関する変数 blockstartX = 20 blockstrtY = 20 blockRowCount = 3 blockColumnCount = 5 blockWidh = 75 blockHeight = 20 blockPadding = 10 blockOffsetTop = 15 blockOffsetLeft = 15 arrBlock = [] #リスト作成 for r in range(blockRowCount): tmpBlock = [] for c in range(blockColumnCount): tmpBlock.append({"x1": 0, "y1": 0, "x2": 0, "y2": 0, "status": 1}) arrBlock.append(tmpBlock) #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval( PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4", outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle( paddleX, paddleY, puddleBottomeRightX, puddleBottomeRightY, fill='#4286f4', outline="") def drawBlock(): for r in range(blockRowCount): for c in range(blockColumnCount): leftXposition = blockstartX + (c * blockWidh) + ( c * blockOffsetLeft) leftYposition = blockstrtY + (r * blockHeight) + ( r * blockOffsetTop) rightXposition = leftXposition + blockWidh rightYposition = leftYposition + blockHeight arrBlock[r][c]["x1"] = leftXposition arrBlock[r][c]["y1"] = leftYposition arrBlock[r][c]["x2"] = rightXposition arrBlock[r][c]["y2"] = rightYposition if arrBlock[r][c]["status"] == 1: canvas.create_rectangle( leftXposition, leftYposition, rightXposition, rightYposition, fill='#4286f4', outline="") #ブロックとの衝突判定 def collisionDetection(x, y): global dy for r in range(blockRowCount): for c in range(blockColumnCount): b = arrBlock[r][c] if ((x + radius + dx > b["x1"] and x - radius -dx < b["x2"]) \ and (y + radius + dy < b["y1"] or y - radius + dy < b["y2"]) \ and b["status"]==1 ): dy = -dy arrBlock[r][c]["status"] = 0 #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX #右方向のパドルの動きを制限する if rightPressed: if paddleX + paddleWidth + paddleSpeed < WIDTH: paddleX = paddleX + paddleSpeed #左方向のパドルの動きを制限する if leftPressed: if paddleX - paddleSpeed > 0: paddleX = paddleX - paddleSpeed #中点を求める centerX = PointOneX + (circleWidth / 2) centerY = PointOneY + (circleWidth / 2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: #ボールの移動先がパドル状の場合は跳ね返す if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: dy = -dy #ボールの移動先にパドルがない場合はゲームオーバーとする else: canvas.delete("all") canvas.create_text( WIDTH / 2, HEIGHT / 2, font=("", 25), fill="black", text="ゲームオーバー") return #dy = -dy #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() drawBlock() collisionDetection(centerX, centerY) root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
ちょっと難しいですが、ブロックを作成する際に、
各ブロックに初期値1のstatusという変数を持たせてます。(59行目)
そして、ブロックとボールが衝突した際にstatusを0にしています。(105-112行目)
drawBlock関数ではif文を用いてstatusが1のブロックのみ表示する様に
処理をしているのでブロックが消えたように見せることができます。(115-126行目)
次はスコアの追加です。
11.デッカチャンゲームの作り方|スコア表示
スコアはブロックを消したときに追加される様にします。
16-17行目:スコアの変数を追加します。
74-77行目:スコアを表示するための関数drawScoreを定義します
117行目:ブロックとボールが衝突した場合スコアを10増やします
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
#================================ # スコア追加 #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #スコア追加 score = 0 #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt( 2*((circleWidth/2)**2)) #座標保存用変数 PointOneX = 235 PointOneY = 255 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 rightPressed = False leftPressed = False paddleSpeed = 15 #破壊ブロックに関する変数 blockstartX = 20 blockstrtY = 30 blockRowCount = 3 blockColumnCount = 5 blockWidh = 75 blockHeight = 20 blockPadding = 10 blockOffsetTop = 15 blockOffsetLeft = 15 arrBlock = [] #リスト作成 for r in range(blockRowCount): tmpBlock = [] for c in range(blockColumnCount): tmpBlock.append({"x1": 0, "y1": 0,"x2": 0, "y2": 0, "status":1}) arrBlock.append(tmpBlock) #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #スコア表示関数 def drawScore(): textScore = tkinter.Label(text="score:"+str(score)) textScore.place(x=0, y=0) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval(PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4",outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle(paddleX, paddleY, puddleBottomeRightX,puddleBottomeRightY, fill = '#4286f4', outline="") def drawBlock(): for r in range(blockRowCount): for c in range(blockColumnCount): leftXposition = blockstartX + (c * blockWidh) + (c * blockOffsetLeft) leftYposition = blockstrtY + (r * blockHeight) + (r * blockOffsetTop) rightXposition = leftXposition + blockWidh rightYposition = leftYposition + blockHeight arrBlock[r][c]["x1"] = leftXposition arrBlock[r][c]["y1"] = leftYposition arrBlock[r][c]["x2"] = rightXposition arrBlock[r][c]["y2"] = rightYposition if arrBlock[r][c]["status"]==1: canvas.create_rectangle(leftXposition, leftYposition, rightXposition, rightYposition, fill='#4286f4', outline="") #ブロックとの衝突判定 def collisionDetection(x,y): global dy,score for r in range(blockRowCount): for c in range(blockColumnCount): b = arrBlock[r][c] if ((x + radius + dx > b["x1"] and x - radius -dx < b["x2"]) \ and (y + radius + dy < b["y1"] or y - radius + dy < b["y2"]) \ and b["status"]==1 ): dy = -dy arrBlock[r][c]["status"] = 0 score = score + 10 #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX #右方向のパドルの動きを制限する if rightPressed: if paddleX + paddleWidth +paddleSpeed < WIDTH: paddleX = paddleX + paddleSpeed #左方向のパドルの動きを制限する if leftPressed: if paddleX - paddleSpeed > 0: paddleX = paddleX - paddleSpeed #中点を求める centerX = PointOneX + (circleWidth/2) centerY = PointOneY + (circleWidth/2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: #ボールの移動先がパドル状の場合は跳ね返す if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: dy = -dy #ボールの移動先にパドルがない場合はゲームオーバーとする else: canvas.delete("all") canvas.create_text(WIDTH/2,HEIGHT/2,font=("",25), fill="black",text="ゲームオーバー") return #dy = -dy #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() drawBlock() drawScore() collisionDetection(centerX,centerY) root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
これは簡単ですね!
ここまでが基礎的なブロック崩しの作成方法です。
続いて「ブロックくずして」の要素を入れていきます。
12.「ブロックくずして」に必要な物
それでは「ブロックくずして」に必要なものを追加していきます。
12-1.パドルがボールを自動的に追いかける処理
これは単純です、ブロック崩しではパドルを左右のキーで動かしていましたが
「ブロックくずして」パドルは自動でボールを追従する必要があるため、
ボールのx座標とパドルのx座標を同期させてしまえばOKです!
下記プログラム(307-308行目)
12-2.デッカチャンの顔を動かす
パドルの代わりにデッカチャンの顔を動かすので、パドルの処理を
デッカチャンに当てはめればOKなのですが、左右だけでなく、上下にも
動かさなければならないため、y軸方向の処理を追加します。
下記プログラム(86-90, 67-77, 130-132, 234-241, 261-304行目)
12-3.ボールとデッカチャンが衝突したら顔を大きくする
ブロックと同じ様に座標でデッカチャンとボールの衝突をチェックし
ボールとデッカチャンが衝突した場合は画像の拡大率を変更します。
ここで衝突数もカウントし、衝突回数が3回を超えた場合に
ゲームオーバーとします。
下記プログラム(91, 165-182, 346-368行目)
ブロック崩しに上記の機能を入れたプログラムがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
#================================ # クリア作成 #================================ #ウィンドウ作成用のモジュールを読み込み import tkinter import math #ウィンドウ生成 root = tkinter.Tk() #サイズ WIDTH = 480 HEIGHT = 320 #スコア追加 score = 0 clearFlg = False #円を含む四角の一辺 circleWidth = 20 #半径 radius = math.sqrt( 2*((circleWidth/2)**2)) #座標保存用変数 PointOneX = 235 PointOneY = 255 PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth #移動差分 dx = -10 dy = -10 #パドル用変数 paddleHeight = 10 paddleWidth = 75 paddleY = HEIGHT - paddleHeight paddleX = (WIDTH - paddleWidth) / 2 #パドルボタン操作 #rightPressed = False #leftPressed = False paddleSpeed = 15 #破壊ブロックに関する変数 blockstartX = 20 blockstrtY = 30 blockRowCount = 3 blockColumnCount = 5 blockWidh = 75 blockHeight = 20 blockPadding = 10 blockOffsetTop = 15 blockOffsetLeft = 15 arrBlock = [] #--------------- #顔画像読み込み #--------------- imgPath = "images/dc.png" originaFaceImage = tkinter.PhotoImage(file=imgPath) faceSize = 7 FaceImage = originaFaceImage.subsample(faceSize) #---------------------------- # 顔の上下左右端の座標を算出する #---------------------------- def getFx1(x): return x - FaceImage.width()/2.8 def getFx2(x): return x + FaceImage.width()/2.8 def getFy1(y): return y - FaceImage.height()/2.8 def getFy2(y): return y + FaceImage.height()/2.8 #------------------------ # 顔画像用変数 #------------------------ #画像座標 faceX = 200 faceY = 200 rightPressed = False leftPressed = False upPressed = False downPressed = False faceSpeed = 10 faceLife = 3 faceDamageCount = 10 #リスト作成 for r in range(blockRowCount): tmpBlock = [] for c in range(blockColumnCount): tmpBlock.append({"x1": 0, "y1": 0,"x2": 0, "y2": 0, "status":1}) arrBlock.append(tmpBlock) #ウィンドウのタイトルを設定 root.title("サンプルゲーム") root.minsize(WIDTH, HEIGHT) #キャンバスの作成 canvas = tkinter.Canvas(bg="white", width=WIDTH, height=HEIGHT) canvas.place(x=-3, y=-3) #スコア表示関数 def drawScore(): textScore = tkinter.Label(text="score:"+str(score)) textScore.place(x=0, y=0) #ボール描写を関数化 def drawBall(): PointTwoX = PointOneX + circleWidth PointTwoY = PointOneY + circleWidth canvas.create_oval(PointOneX, PointOneY, PointTwoX, PointTwoY, fill="#4286f4",outline="") #パドル描写を関数化 def drawPaddle(): puddleBottomeRightX = paddleX + paddleWidth puddleBottomeRightY = paddleY + paddleHeight canvas.create_rectangle(paddleX, paddleY, puddleBottomeRightX,puddleBottomeRightY, fill = '#4286f4', outline="") #------------------------- # 顔画像表示 #-------------------------- def drawFaceImage(): global FaceImage canvas.create_image(faceX, faceY,image=FaceImage,tag="face") def drawBlock(): for r in range(blockRowCount): for c in range(blockColumnCount): leftXposition = blockstartX + (c * blockWidh) + (c * blockOffsetLeft) leftYposition = blockstrtY + (r * blockHeight) + (r * blockOffsetTop) rightXposition = leftXposition + blockWidh rightYposition = leftYposition + blockHeight arrBlock[r][c]["x1"] = leftXposition arrBlock[r][c]["y1"] = leftYposition arrBlock[r][c]["x2"] = rightXposition arrBlock[r][c]["y2"] = rightYposition if arrBlock[r][c]["status"]==1: canvas.create_rectangle(leftXposition, leftYposition, rightXposition, rightYposition, fill='#4286f4', outline="") #ブロックとの衝突判定 def collisionDetection(x,y): global dy,score,clearFlg for r in range(blockRowCount): for c in range(blockColumnCount): b = arrBlock[r][c] if ((x + radius + dx > b["x1"] and x - radius -dx < b["x2"]) \ and (y + radius + dy < b["y1"] or y - radius + dy < b["y2"]) \ and b["status"]==1 ): dy = -dy arrBlock[r][c]["status"] = 0 score = score + 1 if score == blockRowCount * blockColumnCount: clearFlg = True #----------------------------- # ブロックと顔との衝突判定 #----------------------------- def faceCollisionDetection(x,y): print(faceX, faceY) global dy,score,clearFlg,upPressed, downPressed, leftPressed, rightPressed for r in range(blockRowCount): for c in range(blockColumnCount): b = arrBlock[r][c] if ((x + radius + faceSpeed > b["x1"] and x - radius -faceSpeed < b["x2"]) \ and (y + radius + faceSpeed < b["y1"] or y - radius + faceSpeed < b["y2"]) \ and b["status"]==1): #upPressed = False #downPressed = False #leftKeyRelease = False #rightKeyRelease = False return False return True #右キーイベント(押した時) def rightKey(event): global rightPressed rightPressed = True #右キーイベント(離した時) def rightKeyRelease(event): global rightPressed rightPressed = False #左キーイベント(押した時) def leftKey(event): global leftPressed leftPressed = True #左キーイベント(離した時) def leftKeyRelease(event): global leftPressed leftPressed = False #---------------------- # 上下キーイベント追加 #---------------------- #上キーイベント(押した時) def upKey(event): global upPressed upPressed = True #上キーイベント(離した時) def upKeyRelease(event): global upPressed upPressed = False #下キーイベント(押した時) def downKey(event): global downPressed downPressed = True #下キーイベント(離した時) def downKeyRelease(event): global downPressed downPressed = False root.bind("<Right>", rightKey) root.bind("<KeyRelease-Right>", rightKeyRelease) root.bind("<Left>", leftKey) root.bind("<KeyRelease-Left>", leftKeyRelease) #---------------------- # 上下キーイベント追加 #---------------------- root.bind("<Up>", upKey) root.bind("<KeyRelease-Up>", upKeyRelease) root.bind("<Down>", downKey) root.bind("<KeyRelease-Down>", downKeyRelease) #繰り返し処理の定義 def game_loop(): #グローバル変数を利用することを明記 global PointOneX, PointOneY global PointTwoX, PointTwoY global dx, dy global paddleX global faceX global faceY global faceLife,faceSize,FaceImage, faceDamageCount if clearFlg == True: canvas.delete("all") canvas.create_text(WIDTH/2,HEIGHT/2,font=("",25), fill="black",text="ゲームクリア!") return #---------------------------------------------------------------------------------- #右方向の顔の動きを制限する #---------------------------------------------------------------------------------- if rightPressed: #if paddleX + paddleWidth +paddleSpeed < WIDTH: # paddleX = paddleX + paddleSpeed if getFx2(faceX) +faceSpeed < WIDTH and faceCollisionDetection(getFx2(faceX) + faceSpeed, faceY): faceX = faceX + faceSpeed #faceX2 = faceX + (428/7)/2 pass #---------------------------------------------------------------------------------- #左方向の顔の動きを制限する #---------------------------------------------------------------------------------- if leftPressed: #if paddleX - paddleSpeed > 0: # paddleX = paddleX - paddleSpeed if getFx1(faceX) - faceSpeed > 0 and faceCollisionDetection(getFx1(faceX) - faceSpeed, faceY): faceX = faceX - faceSpeed pass #---------------------------------------------------------------------------------- #上方向の顔の動きを制限する #---------------------------------------------------------------------------------- if upPressed: #if paddleX + paddleWidth +paddleSpeed < WIDTH: # paddleX = paddleX + paddleSpeed print(faceX, faceY) if getFy1(faceY) -faceSpeed > 0 and faceCollisionDetection(faceX, getFy1(faceY) - faceSpeed): faceY = faceY - faceSpeed pass #---------------------------------------------------------------------------------- #下方向の顔の動きを制限する #---------------------------------------------------------------------------------- if downPressed: #if paddleX - paddleSpeed > 0: # paddleX = paddleX - paddleSpeed if getFy2(faceY) + faceSpeed < HEIGHT and faceCollisionDetection(faceX, getFy2(faceY) + faceSpeed): faceY = faceY + faceSpeed pass #ボールに追従させる paddleX = PointOneX - circleWidth #中点を求める centerX = PointOneX + (circleWidth/2) centerY = PointOneY + (circleWidth/2) #右にボールが抜けそうな場合反射させる if centerX + radius + dx > WIDTH: dx = -dx #左にボールが抜けそうな場合反射させる if centerX + dx < radius: dx = -dx #下にボールが抜けそうな場合反射させる if centerY + radius + dy > HEIGHT: #ボールの移動先がパドル状の場合は跳ね返す if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: dy = -dy if rightPressed: dx = dx + 3 if leftPressed: dx = dx - 3 #ボールの移動先にパドルがない場合はゲームオーバーとする #else: #pass #canvas.delete("all") #canvas.create_text(WIDTH/2,HEIGHT/2,font=("",25), fill="black",text="ゲームオーバー") #eturn #上にボールが抜けそうな場合反射させる if centerY + dy < radius: dy = -dy #---------------------------------- # ボールと顔が当たるとゲームオーバー #---------------------------------- #if centerX + dx > paddleX and centerX + dx < paddleX + paddleWidth: if (centerX + dx > getFx1(faceX) and centerX + dx < getFx2(faceX)) and\ (centerY + dy > getFy1(faceY) and centerY + dy < getFy2(faceY)): if(faceLife > 0 and faceDamageCount == 10): faceLife = faceLife - 1 faceSize = faceSize -2 faceDamageCount = faceDamageCount -1 FaceImage = originaFaceImage.subsample(faceSize) elif(faceLife <= 0 and faceDamageCount == 10): canvas.delete("all") canvas.create_text(WIDTH/2,HEIGHT/2,font=("",25), fill="black",text="ゲームオーバー") return if faceDamageCount < 10: faceDamageCount = faceDamageCount - 1 if faceDamageCount == 0: faceDamageCount = 10 print(faceDamageCount) PointOneX = PointOneX + dx PointOneY = PointOneY + dy #canvasを全て消す canvas.delete("all") #canvasの内容を再描写 drawBall() drawPaddle() drawBlock() drawScore() #顔画像表示-------------------------------- drawFaceImage() #---------------------------------------- collisionDetection(centerX,centerY) root.after(50, game_loop) game_loop() #ウィンドウの表示とメインループ root.mainloop() |
実行結果
python環境と顔の画像があればコピペで動くので是非一度試してみてください!
友達の顔入れたらウケるかも?