スイカゲーム風のゲームを作る

# 0.はじめに
 今回は世間で大流行中の[スイカゲーム](https://store-jp.nintendo.com/list/software/70010000043363.html)風のゲームをPythonで作成していきたいと思います。
ちなみに、今までゲームを作ったことはありません！
ですので、セオリーとは異なるかもしれません。
```:環境
Windows10 
VScode
Python
```
-　完成すると・・・
![SnapCrab_NoName_2023-11-15_20-41-21_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531555/b0df09fe-02ee-0ac4-c974-8f7b6ea4ad6a.png)

# 1.ライブラリのインポート
　はじめに今回使用するライブラリ等のインポートを記述します
 ```Python:game.py
 import pygame
import pymunk
import pymunk.pygame_util
import random
import cProfile #ゲームが重くなった時に、調査するために使用しました
import pstats
 ```
 # 2.画像を用意しよう
 今回私は、フリーイラストサイトから国旗の画像をダウンロードして、スイカゲームならぬGDPゲームを作成しようと考えました！
 用意したのは、GDPの上から9か国の国旗です。
 スイカ→アメリカ
 メロン→中国
 のように当てはめる計画です。
 画像をダウンロードしたら、GIMPなどを使って円形に画像を切り取ってPNG形式で保存しましょう。
 今回のプログラムでは各画像は、
 **ball_{size}.png**
 （sizeには、数値が入ります）
 と保存しました。
 **ここで画像を適当に作ると当たり判定がガバガバになるので注意してください**
 # 3.変数等の記述
 次は、ウィンドウのサイズや関数内で使用する変数を定義します
 ```Python:Pygameの初期設定
pygame.init()
width, height = 500, 500
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Contry Merge Game")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 36)
 ```
 ```Python:# Pymunkの設定
 space = pymunk.Space()
space.gravity = (0, 900)  # 重力を下向きに設定
space.collision_slop = 0.5  # 衝突のゆるさを設定
space.collision_persistence = 10  # 衝突の持続フレーム数を設定
 ```
 ```Python:その他
 # フルーツ(ボール)のサイズリスト
ball_sizes = [20,30,40,50,60,70,80,90,100]
# 画像のロードをここで一度だけ行う
ball_images = load_images() 
# スコアとハイスコア
score = 0
high_score = 0

# ゲームオーバーフラグ
game_over = False

# 次のボールサイズ
next_ball_size = random.randrange(20, 40,10)
# 衝突ハンドラの設定
handler = space.add_collision_handler(0, 0)
handler.begin = merge_balls

# 箱の座標
box_left = width * 0.1
box_right = width * 0.9
box_bottom = height * 0.9
box_top = height * 0.1

# ゲーム空間の境界を定義
box_boundaries = {'left': box_left, 'right': box_right, 'bottom': box_bottom}
 ```
 # 4.関数,メソッド
 ```Python:画像をロードする関数
 # 画像をロードする関数
def load_images():
    images = {}
    for size in ball_sizes:
        image = pygame.image.load(f'C:\\Users\\ball_{size}.png').convert_alpha()
        images[size] = pygame.transform.scale(image, (size*2, size*2))
    return images
 ```
 ```Python:ボールを作成する関数
def create_ball(space, radius, position):
    global score
    body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, radius))
    body.position = position
    shape = pymunk.Circle(body, radius)
    shape.elasticity = 0.7
    shape.friction = 0.5
    shape.size = radius
    shape.image = ball_images[radius]
    space.add(body, shape)
    score += radius  # スコアを更新
    return shape
 ```
 ```Python:ボールを合体させる関数
def merge_balls(arbiter, space, data):
    global score, high_score
    a, b = arbiter.shapes
    if isinstance(a, pymunk.Circle) and isinstance(b, pymunk.Circle):
        # 接触点の深さを取得
        contact_points = arbiter.contact_point_set
        if all(contact.distance < 0.1 for contact in contact_points.points):
            if a.size == b.size:
                if a.size in ball_sizes[:-1]:
                    # 最大サイズでない場合、次のサイズのボールを生成
                    next_size_index = ball_sizes.index(a.size) + 1
                    new_size = ball_sizes[next_size_index]
                    position = (a.body.position + b.body.position) / 2
                    space.remove(a, a.body, b, b.body)
                    create_ball(space, new_size, position)
                    score += new_size  # スコアを更新
                else:
                    # 最大サイズの場合は合体せずスコアを更新
                    position = (a.body.position + b.body.position) / 2
                    space.remove(a, a.body, b, b.body)
                    create_ball(space, a.size, position)
                    score += a.size * 2  # 最大サイズのボールで合体したので、スコアをさらに増やす
                # ハイスコアを更新
                if score > high_score:
                    high_score = score
                return False  # 合体したので衝突を解消する
    return True
 ```
 ```Python:箱を生成する関数
def create_box(space, box_left, box_top, box_right, box_bottom):
    # 左の壁
    left_wall = pymunk.Segment(space.static_body, (box_left, box_bottom), (box_left, box_top), 1)
    left_wall.elasticity = 0.4
    left_wall.friction = 0.5

    # 右の壁
    right_wall = pymunk.Segment(space.static_body, (box_right, box_bottom), (box_right, box_top), 1)
    right_wall.elasticity = 0.4
    right_wall.friction = 0.5

    # 底の壁
    bottom_wall = pymunk.Segment(space.static_body, (box_left, box_bottom), (box_right, box_bottom), 1)
    bottom_wall.elasticity = 0.4
    bottom_wall.friction = 0.5

    space.add(left_wall, right_wall, bottom_wall)
 ```

 ```Python:箱を描画する関数
def draw_box():
    pygame.draw.line(screen, (255, 255, 255), (box_left, box_bottom), (box_left, box_top), 2)
    pygame.draw.line(screen, (255, 255, 255), (box_left, box_bottom), (box_right, box_bottom), 2)
    pygame.draw.line(screen, (255, 255, 255), (box_right, box_bottom), (box_right, box_top), 2)
 ```
 ```Python:次のフルーツ（国旗）が何かを表示する関数
def show_next_ball():
    ball_image = ball_images[next_ball_size]
    ball_rect = ball_image.get_rect()
    ball_rect.topright = (width - 10, 10)
    screen.blit(ball_image, ball_rect)
 ```
 ```Python:ゲームオーバーを管理する関数
 def game_over():
    global running  # グローバル変数を関数内で使用する宣言
    # ゲームオーバー時のテキストを画面に描画する
    game_over_text = font.render("Game Over!", True, (255, 255, 255))
    text_rect = game_over_text.get_rect(center=(width/2, height/2))
    screen.blit(game_over_text, text_rect)
    pygame.display.flip()
    # 一定時間待ってからゲームを終了
    pygame.time.wait(2000)
    running = False  # ゲームの実行フラグを更新
```

# 5.最終的なプログラム
```Python:game.py
# -*- coding: utf-8 -*-
import pygame
import pymunk
import pymunk.pygame_util
import random
import cProfile
import pstats
# Pygameの初期設定
pygame.init()
width, height = 500, 500
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Country Merge Game")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 36)

# Pymunkの設定
space = pymunk.Space()
space.gravity = (0, 900)  # 重力を下向きに設定
space.collision_slop = 0.5  # 衝突のゆるさを設定
space.collision_persistence = 10  # 衝突の持続フレーム数を設定
running = True 
# ボールのサイズリスト
ball_sizes = [20, 30, 40, 50,60,70,80,90,100]
# ゲームの初期化部分でballsリストを作成
balls = []
# 画像をロードする関数
def load_images():
    images = {}
    for size in ball_sizes:
        image = pygame.image.load(f'C:\\Users\\ball_{size}.png').convert_alpha()
        images[size] = pygame.transform.scale(image, (size*2, size*2))
    return images

# 画像のロードを一度だけ行う
ball_images = load_images() 

# スコアとハイスコア
score = 0
high_score = 0

# ゲームオーバーフラグ
game_over = False

# 次のボールサイズ
next_ball_size = random.randrange(20, 40,10)

# ボールを作成する関数
def create_ball(space, radius, position):
    global score
    body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, radius))
    body.position = position
    shape = pymunk.Circle(body, radius)
    shape.elasticity = 0.7
    shape.friction = 0.5
    shape.size = radius
    shape.image = ball_images[radius]
    space.add(body, shape)
    score += radius  # スコアを更新
    # ボールをballsリストに追加
    balls.append(shape)
    return shape

# 合体して新しいボールを生成する関数
def merge_balls(arbiter, space, data):
    global score, high_score
    a, b = arbiter.shapes
    if isinstance(a, pymunk.Circle) and isinstance(b, pymunk.Circle):
        # 接触点の深さを取得
        contact_points = arbiter.contact_point_set
        if all(contact.distance < 0.1 for contact in contact_points.points):
            if a.size == b.size:
                if a.size in ball_sizes[:-1]:
                    # 最大サイズでない場合、次のサイズのボールを生成
                    next_size_index = ball_sizes.index(a.size) + 1
                    new_size = ball_sizes[next_size_index]
                    position = (a.body.position + b.body.position) / 2
                    space.remove(a, a.body, b, b.body)
                    create_ball(space, new_size, position)
                    score += new_size  # スコアを更新
                else:
                    # 最大サイズの場合は合体せずスコアを更新
                    position = (a.body.position + b.body.position) / 2
                    space.remove(a, a.body, b, b.body)
                    create_ball(space, a.size, position)
                    score += a.size * 2  # 最大サイズのボールで合体したので、スコアをさらに増やす
                # ハイスコアを更新
                if score > high_score:
                    high_score = score
                return False  # 合体したので衝突を解消する
    return True



# 衝突ハンドラの設定
handler = space.add_collision_handler(0, 0)
handler.begin = merge_balls

# 箱の座標
box_left = width * 0.1
box_right = width * 0.9
box_bottom = height * 0.9
box_top = height * 0.1
# 箱の壁を作成する関数
def create_box(space, box_left, box_top, box_right, box_bottom):
    # 左の壁
    left_wall = pymunk.Segment(space.static_body, (box_left, box_bottom), (box_left, box_top), 1)
    left_wall.elasticity = 0.4
    left_wall.friction = 0.5

    # 右の壁
    right_wall = pymunk.Segment(space.static_body, (box_right, box_bottom), (box_right, box_top), 1)
    right_wall.elasticity = 0.4
    right_wall.friction = 0.5

    # 底の壁
    bottom_wall = pymunk.Segment(space.static_body, (box_left, box_bottom), (box_right, box_bottom), 1)
    bottom_wall.elasticity = 0.4
    bottom_wall.friction = 0.5

    space.add(left_wall, right_wall, bottom_wall)
# 箱の壁を物理空間に追加
create_box(space, box_left, box_top, box_right, box_bottom)


# 箱を描画する関数
def draw_box():
    pygame.draw.line(screen, (255, 255, 255), (box_left, box_bottom), (box_left, box_top), 2)
    pygame.draw.line(screen, (255, 255, 255), (box_left, box_bottom), (box_right, box_bottom), 2)
    pygame.draw.line(screen, (255, 255, 255), (box_right, box_bottom), (box_right, box_top), 2)

# 次のボールを表示する関数
def show_next_ball():
    ball_image = ball_images[next_ball_size]
    ball_rect = ball_image.get_rect()
    ball_rect.topright = (width - 10, 10)
    screen.blit(ball_image, ball_rect)

def game_over():
    global running  # グローバル変数を関数内で使用する宣言
    # ゲームオーバー時のテキストを画面に描画
    game_over_text = font.render("Game Over!", True, (255, 255, 255))
    text_rect = game_over_text.get_rect(center=(width/2, height/2))
    screen.blit(game_over_text, text_rect)
    pygame.display.flip()
    # 一定時間待ってからゲームを終了
    pygame.time.wait(2000)
    running = False  # ゲームの実行フラグを更新

# ゲーム空間の境界
box_boundaries = {'left': box_left, 'right': box_right, 'bottom': box_bottom}

# ゲームのメインループ
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            if box_left < mouse_x and box_right > mouse_x and box_top > mouse_y:
                create_ball(space, next_ball_size, (mouse_x, mouse_y))
                next_ball_size = random.randrange(20, 40,10)  # 次のボールを更新

    # 物理演算の精度を下げる（メインループ内）
    space.step(1/30)  # 演算の更新間隔を増やす

    # 画面をクリア
    screen.fill((0, 0, 0))

    # 箱を描画
    draw_box()

    # すべてのボールに対して画像を描画
    for shape in space.shapes:
        if isinstance(shape, pymunk.Circle) and hasattr(shape, "image"):
            ball_image = shape.image
            pos = shape.body.position.x - shape.radius, shape.body.position.y - shape.radius
            screen.blit(ball_image, pos)

    # ボールが画面外に出たかどうかチェック
    for ball in balls:
        ball_position = ball.body.position
        if ball_position.x < box_boundaries['left'] or ball_position.x > box_boundaries['right'] or ball_position.y > box_boundaries['bottom']:
            running = False  # runningフラグをFalseに設定
            game_over()  # ゲームオーバー処理を呼び出し
    # 次のボールを表示
    show_next_ball()

    # スコアを表示
    score_text = font.render(f"Score: {score}", True, (255, 255, 255))
    screen.blit(score_text, (10, 10))

    # ハイスコアを表示
    high_score_text = font.render(f"High Score: {high_score}", True, (255, 255, 255))
    screen.blit(high_score_text, (10, 50))

    pygame.display.flip()
    clock.tick(30)
# ゲームオーバー後の待機ループ
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()



```
 # 課題点
- 処理が重くなる場合がある
 - フルーツ（ボール）の当たり判定が甘い
 - フルーツ（ボール）が箱の底を貫通する時がある
 - BGMやフルーツの回転を実装したい
 - 本家と比べるとフルーツ（ボール）の動作に味がない（物理的すぎる）
 - Unityで実装したい
 - 画面に華がない
 # 最後に
 　プログラムを実行させて修正しながら記事を書いたので、細かい修正や解説は後々追加していきます
  また、[スイカゲーム　作り方](https://www.google.com/search?q=%E3%82%B9%E3%82%A4%E3%82%AB%E3%82%B2%E3%83%BC%E3%83%A0+%E8%87%AA%E4%BD%9C&sca_esv=582608004&sxsrf=AM9HkKmNeOZQZF-mfrq1I2tDIw6SjDEMvg%3A1700048805064&ei=pa9UZf_BA8Dh1e8Pt_2-mAM&ved=0ahUKEwi_qIGp98WCAxXAcPUHHbe-DzMQ4dUDCBA&uact=5&oq=%E3%82%B9%E3%82%A4%E3%82%AB%E3%82%B2%E3%83%BC%E3%83%A0+%E8%87%AA%E4%BD%9C&gs_lp=Egxnd3Mtd2l6LXNlcnAiGeOCueOCpOOCq-OCsuODvOODoCDoh6rkvZwyBBAjGCdI_w5QlARY3wpwAXgBkAEAmAFmoAGCBaoBAzYuMbgBA8gBAPgBAcICChAAGEcY1gQYsAPCAgYQABgDGATCAgYQABgEGB7CAgcQIxiwAhgn4gMEGAAgQYgGAZAGCg&sclient=gws-wiz-serp)と検索してYoutubeや様々なサイトを参考に作成しました。