チャプター1とチャプター2を通してpygameの基礎を学んできました。
今回からいよいよ本格的なゲーム制作に入っていきます。
Chapter1:pygame入門|画面を表示しよう
Chapter2:簡単なノベルゲームを作ろう
Chapter3:簡単なアクションゲームを作ろう
・Chapter3-1:衝突判定に基本を理解しよう ◁今回はここ
・Chapter3-2:画面に敵を配置しよう
・Chapter3-3:ゲームオーバーを設定しよう
・Chapter3-4:ゲームクリアを設定しよう
・Chapter3-5:追跡してくる敵を実装しよう
Chapter4:ブロック崩しを作ろう
Chapter5:シューティングゲームを作ろう
Chapter6:横スクロールのアクションゲームを作ろう
チャプター2までの内容で、プレイヤーキャラクターを画面上で自由に操作できるようになりました。
しかし、現時点ではどこまでも動けてしまい、画面外に出ることさえ可能です。これではゲームとしての制限やルールが不十分で、プレイヤーの行動を適切に制御することができません。
ゲームを作るうえで欠かせないのが「衝突判定」という仕組みです。
これはキャラクターが他の物体とぶつかったかどうかを検出し、それ応じて動作を変える技術です。たとえば、壁にぶつかったら進めない、敵にぶつかったらゲームオーバー、といった処理に使われます。
この章では、まず画面の四辺に「壁」を設置し、プレイヤーがそれ以上進めないようにする方法を学びます。
簡単なアクションゲーム制作の準備
まずはダウンロードしたzipファイルを展開し、その中の「action.py」を実行してください。
芝生の背景画面に操作キャラの猫が表示されており、猫はキーボードの矢印キーで操作できます。
今回(Chapter3-1)の目標は、画面の端に壁を作って猫が画面の外に出ていかないようにすることです。
また「action.py」は今回だけでなくChapter3-5まで通して使用します。
毎回少しずつコードを書き足していき、最終的にアクションゲームを完成させましょう。
衝突判定とは?|仕組みと使い方を解説
ゲームの中でキャラクターが自由に動けるのは楽しいことですが、あらゆる場所に行けてしまっては、ゲームとして成立しません。
そこで重要になるのが「衝突判定(Collision Detection)」です。
これは、ゲーム内のオブジェクト同士が接触したかどうかを判断する処理のことを指します。衝突判定を使うことで、以下のような動作を実現できます。
- プレイヤーが壁にぶつかったら、これ以上先に進めなくなる。
- 敵に当たったら、ゲームオーバーになる。
- ゴールに触れたら、ステージクリアになる。
このように、衝突判定は「ゲームのルールを実現するための柱」とも言える存在なのです。
本記事では、まず最も基本的な衝突判定である「プレイヤーと壁の衝突」を実装していきます。
プレイヤーが壁にぶつかった時に、それ以上動けないように制御する処理を追加していきましょう。
Rectオブジェクトで衝突を検出しよう
Pygameで衝突判定を行う際に欠かせないのが、Rect
(レクト)オブジェクトです。
Rect
は、四角形(Rectangle)の略で、ゲーム内のオブジェクトの位置とサイズを管理するためのものです。
例えば、次のようなコードで猫の位置を表すRect
を作成しています。
neko_rect = pygame.Rect(50, 50, 50, 50)
このコードは、「x座標50、y座標50の位置に、幅50、高さ50の四角形(猫)を作る」という意味です。
このRect
は、見た目の画像(猫のイラスト)とセットで使われ、移動処理や衝突判定にも活用されます。
なぜRect
が便利かというと、Pygameが提供する衝突判定用の関数やメソッドが、このRect
に対して使えるからです。たとえば:
- 他の
Rect
との衝突を調べるには:colliderect()
メソッド - 複数の
Rect
との衝突を一括で調べるには:collidelist()
メソッド
これらの関数を使うことで、例えば「プレイヤーが壁にぶつかっているか?」といった判定を、簡単にプログラムで実現できるようになります。
このあと実際に、壁にもRect
を使って四角形の当たり判定を設定し、猫と壁との衝突をcollidelist()
で調べていく処理を加えていきます。
壁リスト(walls)を追加して障害物を設置
衝突判定を行うには、相手となる壁もRect
としてゲーム内に存在させる必要があります。
今回は、ゲーム画面の四辺(上・下・左・右)に壁を設置して、プレイヤーがその外に出られないように制限をかけます。
壁も猫と同様にpygame.Rect
を使って定義します。壁は全部で4つ作り、それぞれの大きさと位置を設定して、リストにまとめておくと管理が簡単になります。
以下は、壁を定義してwalls
というリストにまとめるコードです。
このコードでは、画面のサイズが800×600であることを前提として、以下のように四角形を配置しています:
- 上下の壁は画面の上下端に横長に設置(幅800、高さ20)
- 左右の壁は縦長で、画面の左右端に設置(幅20、高さ600)
これらの壁をwalls
というリストにまとめておくことで、後でループ処理を使って一括で処理できるようになります。
初心者の方にとっては、「なぜリストを使うのか?」という疑問が出てくるかもしれません。リストを使う最大の理由は、「同じような処理をまとめて行える」ことです。
今回のように「すべての壁に対して衝突判定したい」とき、一つずつコードを書くのではなく、リストにしておけばfor
文で一括処理ができ、コードがすっきりします。
リストが何か分からない人は、↓↓の記事を読んで学習しましょう。
衝突検出メソッドの使い方|colliderect()
とcollidelist()
Pygameでは、オブジェクト同士がぶつかったかどうかを調べるために、Rect
オブジェクトに備わっている衝突判定用のメソッドを使います。
よく使われるのが、colliderect()
と collidelist()
の2つです。
これらの使い方を分かりやすく解説します。
colliderect() の役割と実装例
colliderect()
は、2つのRect
同士がぶつかっているかどうかを判定する関数です。使い方は以下の通りです。
if neko_rect.colliderect(enemy_rect): # もし猫のレクトと敵のレクトが重なっているなら print("衝突しました!")
この関数は、衝突していれば True
、衝突していなければ False
を返します。
つまり、if
文の中で使うことで、「ぶつかっていたらこの処理をする」といった使い方ができるのです。
collidelist() の使いどころと違い
collidelist()
は、複数のRect
の中にぶつかっているものがあるかどうかを調べる関数です。
たとえば、画面の四辺にある「壁」リストの中に、ぶつかっている壁があるかを確認したいときに使います。
index = neko_rect.collidelist(walls) # リストwallsの中のぶつかっている番号を変数indexに代入 if index != -1: print("どこかの壁にぶつかっています!")
collidelist()
は、衝突している最初の要素の「インデックス番号(0, 1, 2…)」を返す- どれとも衝突していない場合は
-1
を返す
つまり、以下のコードは「猫が壁リストの中のどれかと衝突しているか?」を判定することができます。
if neko_rect.collidelist(walls) != -1:
「!= -1」
という条件を使うことで、「衝突がある(-1ではない)」という状態を見分けているのです。
猫キャラと壁の接触を判定する実装例
猫(プレイヤー)がその壁にぶつかったときに、それ以上進めないようにする処理を追加していきます。
使うのはPygameのRect
オブジェクトに備わっている collidelist()
メソッド です。
以下はその具体的なコードです。猫の移動処理の中に追加することで、壁とぶつかった際に元の位置へ戻すことができます。
この処理のポイントは次の通りです:
- 猫の移動量(vx, vy)を一旦適用して、位置を更新します。
- 更新後の猫の位置が、
walls
リストのどれかと衝突していないかを調べます。 - 衝突していた場合、移動前の位置に戻して「ぶつかったから動けない」という状態を作ります。
このように、collidelist()
を使うことで、複数の壁に対してまとめて衝突判定が行えるため、とても便利です。
この判定処理を組み込めば、もう猫が壁の外に出てしまうことはありません。ゲームにしっかりとした「枠」ができて、より自然な動きになります。
壁の描画処理を追加する
前の章で猫と壁との衝突判定ができるようになりましたが、今のままだと画面上には壁が表示されていないため、プレイヤーには「どこに壁があるのか」が分かりません。
そこでこの章では、先ほど作成したwalls
リスト内の四角形(Rect
)を画面に表示する処理を追加します。
壁を表示するには、pygame.draw.rect()
という関数を使います。
このコードは、walls
リスト内の各壁を一つずつ取り出して、濃い緑色(DARKGREEN)で画面に四角形として描画する処理です。
これをgamestage()
関数の中に追加することで、ゲーム画面に明確な壁が表示され、プレイヤーがどこまで動けるかが一目でわかるようになります。
ゲームの「見た目」と「動き」が一致して、より自然なプレイ感を実現できます。
まとめ
今回は「衝突判定の基本を理解しよう」というテーマで、プレイヤーが壁にぶつかったときに移動できないようにする処理を学びました。
今回の学習を通じて、プレイヤーの動きをコントロールするために必要な「当たり判定」の基礎を理解することができたと思います。ゲームのルールはこうした積み重ねによって少しずつ形作られていきます。
次回は、いよいよ敵キャラクターをゲーム画面に表示します。より緊張感のあるゲームづくりに一歩踏み出していきましょう。
お疲れさまでした!
- Chapter3-1の完成コード
-
今回の記事での完成するコード全体は↓↓の通りです。
必要な方は開いて確認しましょう。
# 初期化(ゲームの準備をする) import pygame, sys, random pygame.init() screen = pygame.display.set_mode((800,600)) ## 背景画像の設定 haikei_img = pygame.image.load("images/shibafu.png") haikei_img = pygame.transform.scale(haikei_img, (800, 600)) ## プレイヤーの猫の設定 neko_imgR = pygame.image.load("images/neko.png") neko_imgR = pygame.transform.scale(neko_imgR, (50,50)) neko_imgL = pygame.transform.flip(neko_imgR, True, False) neko_rect = pygame.Rect(50,50,50,50) # 猫のRect ## 敵の犬の設定 ## 敵の幽霊の設定 ## ゴールの設定 # 壁の設定(四辺に配置) walls = [ pygame.Rect(0, 0, 800, 20), # 上の壁 pygame.Rect(0, 0, 20, 600), # 左の壁 pygame.Rect(780, 0, 20, 600), # 右の壁 pygame.Rect(0, 580, 800, 20) # 下の壁 ] ## リセットボタンの設定 ## メインループ内で使う変数 rightFlag = True # 猫の向き # リセットボタンが押されたか # 表示するページ(1:ゲーム画面、2:ゲームオーバー画面、3、ゲームクリア画面) # ここから関数の定義 ## ゲームステージ def gamestage(): global rightFlag global page # 画面の初期化 screen.blit(haikei_img, (0,0)) # 猫の移動量 vx = 0 vy = 0 #ユーザー入力 key = pygame.key.get_pressed() if key[pygame.K_RIGHT]: # 右キーが押されたら右へ移動 vx = 4 rightFlag = True if key[pygame.K_LEFT]: # 左キーが押されたら左へ移動 vx = -4 rightFlag = False if key[pygame.K_UP]: # 上キーが押されたら上へ移動 vy = -4 if key[pygame.K_DOWN]: # 下キーが押されたら下へ移動 vy = 4 # 猫の処理 neko_rect.x += vx neko_rect.y += vy # 猫と壁との衝突判定 if neko_rect.collidelist(walls) != -1: # もし猫が壁と衝突していたら neko_rect.x -= vx neko_rect.y -= vy # 猫の向きの指定 if rightFlag: screen.blit(neko_imgR, neko_rect) else: screen.blit(neko_imgL, neko_rect) ## 犬の処理 ## 幽霊の処理 ## ゴールの処理 ## 壁の処理 for wall in walls: # wallsリストの全要素(壁のRect) pygame.draw.rect(screen, pygame.Color("DARKGREEN"),wall) ## ジャンプ関数(ボタンが押されたらnewpageへジャンプする) ## リセット関数(リセットボタンが押されたらゲームをリセットする) ## ゲームオーバー関数 ## ゲームクリア関数 # メインループ while True: gamestage() #画面表示 pygame.display.update() pygame.time.Clock().tick(60) # 終了処理 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit()
FAQ|pygameの衝突判定に関するよくある質問
今回の記事に関する「よくある質問」とその解答について、以下の内容を参考にしてください。
- Q1. 衝突判定にはどのような方法がありますか?
-
衝突判定には、矩形同士の判定(Rect同士のcolliderect)、リスト内の複数オブジェクトとの判定(collidelist)、ピクセル単位の精密な判定などさまざまな方法があります。
pygameでは基本的にRectオブジェクトを使った判定がよく使われます。
- Q2. colliderect()メソッドとcollidelist()メソッドの違いは何ですか?
-
colliderect()は2つのRectオブジェクト同士の衝突判定を行うのに対し、collidelist()は複数のRectをまとめたリストと自分のRectが衝突しているかどうかを一度に判定できます。
複数のオブジェクトと効率的に判定したい場合はcollidelist()が便利です。
- Q3. なぜ衝突判定にRectオブジェクトを使うのですか?
-
Rectオブジェクトは座標とサイズを簡単に管理でき、衝突判定のメソッドも豊富に備わっています。
そのためゲーム制作において素早く安全に判定処理が行えるというメリットがあります。
衝突判定の実装についてよくあるトラブルと解決法
今回の記事に関する「よくあるトラブル」とその原因、解決法について、以下の内容を参考にしてください。
colliderect()
を使っても衝突判定がうまくいかない
- トラブルの原因と対処法を見る
-
トラブルの原因
判定対象のRectオブジェクトの座標やサイズが正しく設定されていない場合が多いです。
特に、移動処理や画像サイズの計算ミスにより、見た目上は重なっているのに判定されないことがあります。
トラブルの解決法
ゲーム画面にデバッグ用の枠線(
pygame.draw.rect()
など)でRect領域を表示し、意図通りの範囲になっているか確認してください。座標やサイズの計算に間違いがないか見直しましょう。
collidelist()
を使っているのに、リスト内のどれとも当たらないことがある
- トラブルの原因と対処法を見る
-
トラブルの原因
衝突判定用のRectリストの中身が毎フレーム正しく更新されていなかったり、想定していない順番や重複が含まれていることがあります。
トラブルの解決法
リストの中身をprintで出力して、毎フレームの状態や値の変化を確認しましょう。
意図したRectだけが入っているか、座標が期待通りになっているか調べることで原因を特定できます。
衝突判定の結果に関わらずキャラクターがめり込んでしまう
- トラブルの原因と対処法を見る
-
トラブルの原因
衝突した後の処理で、キャラクターの位置を調整していない、または処理順が適切でないために、判定後にキャラクターが壁や障害物の内部に移動してしまうことがあります。
トラブルの解決法
衝突検出後に「本来あるべき位置」にキャラクターのRectを戻す処理を加えましょう。
また、衝突判定と移動処理の順番を見直し、まず移動→判定→位置修正の流れになっているか確認してください。