【Python】自由落下ゲーム開発(スイカゲーム風) Part3

こんにちは、にわこまです!

今回は、ボール同士の衝突判定とサンプルボールを作成します。

 

本記事で使用するソースコードを以下に示します。

 

何か分からない点がございましたらご連絡お願いいたします!

メールまたはTwitterのDMまで!

 

 

スポンサードサーチ


ボール同士の衝突判定の作成

ボール同士の衝突判定では、衝突の検知、コリジョンタイプが同じか判定、衝突したボールを削除、新しいボールを作成の順に処理を実施します。

また、新しいボール作成時にスコアを加算する処理も実施します。

 

 

 

コードの提示

衝突判定のソースコードを以下に示します。

import pygame
import pymunk
import random

# 省略

def collide(arbiter, space, data):
    # collided shapes
    a, b = arbiter.shapes
    # collision type
    a_ct = a.collision_type
    b_ct = b.collision_type
    # 衝突処理を実施しない場合を判定
    if(a_ct != b_ct):
        # 異なるBall同士の衝突の場合
        return
    if(a_ct == 11 or b_ct == 11):
        # Ball11同士の衝突の場合
        return
    # 衝突座標を算出
    a_x, a_y = a.body.position
    b_x, b_y = b.body.position
    x = (a_x + b_x) / 2
    y = (a_y + b_y) / 2
    # 削除するBallを求める
    balls_to_remove = []
    for ball in data["balls"]:
        if(ball.shape == a or ball.shape == b):
            balls_to_remove.append(ball)
    # Ballを削除する
    for ball in balls_to_remove:
        space.remove(ball.shape, ball.body)
        data["balls"].remove(ball)
    # Ballの作成かつスコア更新
    if(a_ct == 1 and b_ct == 1):
        data["balls"].append(Ball02(x, y))
        data["score"] += 1
    elif(a_ct == 2 and b_ct == 2):
        data["balls"].append(Ball03(x, y))
        data["score"] += 3
    elif(a_ct == 3 and b_ct == 3):
        data["balls"].append(Ball04(x, y))
        data["score"] += 6
    elif(a_ct == 4 and b_ct == 4):
        data["balls"].append(Ball05(x, y))
        data["score"] += 10
    elif(a_ct == 5 and b_ct == 5):
        data["balls"].append(Ball06(x, y))
        data["score"] += 15
    elif(a_ct == 6 and b_ct == 6):
        data["balls"].append(Ball07(x, y))
        data["score"] += 21
    elif(a_ct == 7 and b_ct == 7):
        data["balls"].append(Ball08(x, y))
        data["score"] += 28
    elif(a_ct == 8 and b_ct == 8):
        data["balls"].append(Ball09(x, y))
        data["score"] += 36
    elif(a_ct == 9 and b_ct == 9):
        data["balls"].append(Ball10(x, y))
        data["score"] += 45
    elif(a_ct == 10 and b_ct == 10):
        data["balls"].append(Ball11(x, y))
        data["score"] += 55
    pass

# 省略

def game():
    # Field
    tlx, tly = 100, 450
    brx, bry = 400, 100
    field = Field(tlx, tly, brx, bry)

    # Ball
    balls = []

    # CollisionHandler
    handler = space.add_default_collision_handler()
    handler.data["balls"] = balls
    handler.data["score"] = 0
    handler.post_solve = collide

    # Game Start
    while(True):
        # 省略


game()
pygame.quit()

 

 

コードの解説

7行目から65行目の「collide関数」は衝突時に実行される処理を定義しています。

 

9行目では衝突した2つのオブジェクトを取得しています。

 

11行目と12行目ではそれぞれコリジョンタイプを取得しています。

 

14行目と16行目では同じコリジョンタイプであるか判定しています。異なるコリジョンタイプである場合はreturnで関数を終了しています。

 

17行目と19行目では最大ボール同士の衝突か判定しています。最大ボール同士の衝突では新しくボールを作成しませんし、スコアも更新しないためreturnで関数を終了しています。

 

21行目から24行目では衝突座標を算出しています。新しいボールは算出した座標に表示されます。

 

26行目から29行目では削除するボールオブジェクトを取得し、削除用ボールリストに追加しています。

 

31行目から33行目ではボールを削除しています。

32行目では自由落下用の画面オブジェクトから削除しています。

33行目では画面に表示するボールを保持するリストからボールを削除しています。

 

35行目から65行目では、それぞれ新しいボールの作成とスコアの更新を実施しています。

コリジョンタイプ1同士の場合はBall02を作成し、スコアに1を加算します。

コリジョンタイプ2同士の場合はBall03を作成し、スコアに3を加算します。

以後同じようにボールの作成と、スコアの加算を実施します。

 

 

79行目から82行目では衝突時に実行する関数やデータを定義しています。

79行目ではデフォルトの衝突ハンドラーを作成しています。

80行目では関数内で使用するボールデータを定義しています。

81行目では関数内で使用するスコアデータを定義しています。

82行目では衝突後に実行する関数を定義しています。

 

 

動作確認

動作確認を実施します。

コードを保存し、コマンドプロンプトを開きます。コードを保存したフォルダまで移動し、以下のコマンドを実行します。

python free_fall_game_part3_1.py

 

以下のように小麦色の画面にフィールドが表示されることを確認します。

 

以下のように同じ大きさのボール同士が衝突した際に新しいボールが作成されることを確認します。

 

 

スポンサードサーチ


サンプルボールの作成

サンプルボールとは、スイカゲームで言うところの画面右下にある「シンカの輪」のことです。

シンカの輪と同じように円形に11種類のボールを表示します。

サンプルボールはBallクラスではなく新しくCircleクラスを作成し表示します。

 

 

コードの提示

サンプルボールのソースコードを以下に示します。

import math
import pygame
import pymunk
import random

# 省略

def convert_cordinates(point):
    return point[0], disp_size[1]-point[1]

def rotate_cordinates(point, d):
    rad = math.radians(d)
    rotated_x = point[0] * math.cos(rad) - point[1] * math.sin(rad)
    rotated_y = point[0] * math.sin(rad) + point[1] * math.cos(rad)
    return rotated_x, rotated_y

# 省略

class Circle(object):
    def __init__(self, x, y, color, radius):
        self.x = x
        self.y = y
        self.color = color
        self.radius = radius
    def draw(self):
        pygame.draw.circle(
            display,
            self.color,
            convert_cordinates((self.x, self.y)),
            self.radius
        )


def game():
    # Field
    tlx, tly = 100, 450
    brx, bry = 400, 100
    field = Field(tlx, tly, brx, bry)

    # Ball
    balls = []

    # CollisionHandler
    handler = space.add_default_collision_handler()
    handler.data["balls"] = balls
    handler.data["score"] = 0
    handler.post_solve = collide

    # Sample Ball
    cx, cy = 600, 200
    sample_circles = [
        Circle(cx, cy, color_01, radius_01),
        Circle(cx, cy, color_02, radius_01),
        Circle(cx, cy, color_03, radius_01),
        Circle(cx, cy, color_04, radius_01),
        Circle(cx, cy, color_05, radius_01),
        Circle(cx, cy, color_06, radius_01),
        Circle(cx, cy, color_07, radius_01),
        Circle(cx, cy, color_08, radius_01),
        Circle(cx, cy, color_09, radius_01),
        Circle(cx, cy, color_10, radius_01),
        Circle(cx, cy, color_11, radius_01),
    ]
    for i, ball in enumerate(sample_circles):
        d = -30 + (-30) * i
        x, y = rotate_cordinates((0, 100), d)
        ball.x += x
        ball.y += y
        pass

    # Game Start
    while(True):
        for event in pygame.event.get():
            if(event.type == pygame.QUIT):
                return
            if(event.type == pygame.MOUSEBUTTONDOWN):
                # 省略
        # Fiil background
        display.fill(WHEAT)

        # Draw Field
        field.draw()

        # Draw Ball
        for ball in balls:
            ball.draw()
       
        # Draw Sample Ball
        for ball in sample_circles:
            ball.draw()

        # Display Update
        pygame.display.update()
        clock.tick(FPS)
        space.step(1/FPS)


game()
pygame.quit()

 

 

コードの解説

1行目ではmathライブラリをいんぽーとしています。mathライブラリは数学系の計算を使いやすくした関数が含まれるライブラリです。このライブラリの三角関数を用いてサンプルボールの表示位置を計算します。

 

11行目から15行目の「rotate_cordinates関数」では任意の座標から任意の角度回転したときの座標を算出する関数です。引数は座標を示すpointと角度を示すdです。座標は(x, y)の形式で設定します。

12行目では角度をラジアン単位に変換しています。

13行目ではx座標をd度回転させた座標を算出しています。

14行目ではy座標をd度回転させた座標を算出しています。

15行目では算出した値を返しています。

 

19行目から31行目のCircleクラスサンプルボール用のクラスを定義しています。

20行目から24行目の「init関数」は初期処理を実施しています。引数はx座標、y座標、色、半径です。25行目から31行目の「draw関数」はCircleクラスを表示する関数です。

 

50行目から69行目ではサンプルボールの作成かつ座標更新処理を実施しています。

50行目ではサンプルボール表示位置の中心となる座標を定義しています。

51行目から63行目ではサンプルボールを作成しています。大きさは同じで色は異なるようにしています。また、一旦座標は全て同じにしています。後述する処理で表示位置座標を更新します。

64行目から69行目では表示位置座標を更新しています。

65行目では30度ずつ右回転するように角度を算出しています。

66行目では先述したrotate_cordinates関数を用いて回転後の座標を算出しています。

67行目ではx座標を更新しています。

68行目ではy座標を更新しています。

 

89行目と90行目ではサンプルボールを表示しています。

 

 

動作確認

動作確認を実施します。

コードを保存し、コマンドプロンプトを開きます。コードを保存したフォルダまで移動し、以下のコマンドを実行します。

python free_fall_game_part3_2.py

 

以下のように画面右下に11種類のボールが表示されていることを確認します。

 

 

スポンサードサーチ


まとめ

今回は、同じ大きさのボール同士が衝突した時に実行する関数とサンプルボールの作成を行いました。

 

次回Part4では、フィールドに落とすボールを保持するCurrentCircleクラスと次ボールを示すNextCircleクラスを作成します。

 

 

本記事で使用したソースコードを以下に示します。

 

 

最後までお読みいただきありがとうございます。

 

 

参考

 


スポンサードサーチ