【Python】ぷよぷよ風の文字連結パズルゲームを作る【あけおめパズル】

スポンサーリンク
PC創作
マイケル
マイケル
餅おいしいな〜〜〜

カタカタカタカタ

エレキベア
エレキベア
やっぱり正月はお餅クマね
マイケル
マイケル
いくらでも食べれるぜ


カタカタカタカタ

エレキベア
エレキベア
・・・何カタカタしてるクマ?
マイケル
マイケル
これかい?
これはね・・・
01 Akeome
マイケル
マイケル
Pythonで作ったあけおめパズル さ!!!
エレキベア
エレキベア
そうなのクマね
マイケル
マイケル
・・・。
エレキベア
エレキベア
・・・。
エレキベア
エレキベア
お餅おいしいクマね
マイケル
マイケル
どんなゲームか聞いてよ!!!
スポンサーリンク

あけおめゲームの設計

マイケル
マイケル
あけおめゲームは一言でいうなら
ぷよぷよ風の文字連結ゲームだ!
エレキベア
エレキベア
(勝手に解説が始まったクマ・・・)
マイケル
マイケル
ルールはこんな感じ!
Screenshot 2021 01 02 22 28 20
マイケル
マイケル
落ちてくる「あけおめ」の文字を探してなぞって消すゲーム!
一つ揃えると最下段3段も消す仕様にしました!
エレキベア
エレキベア
よく見るタイプの落ち物パズルクマね
マイケル
マイケル
テトリスやぷよぷよ系のゲームだね。
参考書は前回と同じくこちらを使用しました!

Pythonでつくる ゲーム開発 入門講座

マイケル
マイケル
この本の落ち物パズルゲームをカスタマイズして今回の動きにしています!
実装方法についてもっと詳しく知りたい方はぜひ読んでみてね!
エレキベア
エレキベア
これは良本クマ〜〜〜〜
スポンサーリンク

あけおめゲームの開発

マイケル
マイケル
それでは開発方法について説明します!
エレキベア
エレキベア
(聞いてないクマけどなぁ)

画像の作成

マイケル
マイケル
まずは使用する画像素材を作成します!

・背景
・落ちてくるボールの画像
・揃った時に帰るボール
・カーソル

の画像を用意しましょう!
Ball bg↑ball_bg.png(背景)
Ball 1
Ball 2
Ball 3
Ball 4
↑ball_1.png 〜 ball_4.png(あけおめボール)
Ball gold 1
Ball gold 2
Ball gold 3
Ball gold 4
Ball gold 5
↑ball_gold_1.png 〜 ball_gold_5.png(謹賀新年ボール、祝ボール)

Ball cursor↑ball_cursor.png(カーソル)
マイケル
マイケル
背景については、今回は枠の大きさを24px
正方形一つあたりの大きさを72pxにしておきます!
また、正方形の数は横8列 * 縦10列で作成します!
Screenshot 2021 01 02 22 38 19↑ステージの大きさ
エレキベア
エレキベア
素材準備完了クマ〜〜〜

処理の実装

マイケル
マイケル
ソース全体は以下のようになります!
import tkinter
import random

###############
# マウス操作用
###############
cursor_x = 0
cursor_y = 0
mouse_x = 0
mouse_y = 0
mouse_c = 0


# マウス移動処理
def mouse_move(e):
    global mouse_x, mouse_y
    mouse_x = e.x
    mouse_y = e.y


# マウスクリック処理
def mouse_press(e):
    global mouse_c
    mouse_c = 1


###############
# ゲーム内変数
###############
index = 0
chain_list = []
generate_timer = 0
delete_timer = 0
ball = []
year = 2020


# ステージの定義
def init_stage():
    global ball
    ball = [
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
    ]


# ボール描画処理
def draw_ball():
    for y in range(10):
        for x in range(8):
            if ball[y][x] > 0:
                cvs.create_image(x*72+60, y*72+60, image=img_ball[ball[y][x]], tag="BALL")


# ボール落下処理
def drop_ball():
    # 下から確認、最下段は除く
    for y in range(8, -1, -1):
        for x in range(8):
            if ball[y][x] != 0 and ball[y+1][x] == 0:
                ball[y+1][x] = ball[y][x]
                ball[y][x] = 0


# テキスト描画処理
def draw_txt(txt, x, y, siz, col, tg):
    fnt = ("sans-serif", siz, "bold")
    # 影も追加
    cvs.create_text(x, y, text=txt, fill=col, font=fnt, tag=tg)
    cvs.create_text(x + 2, y + 2, text=txt, fill="black", font=fnt, tag=tg)


# ゲームメイン処理
def game_main():
    global cursor_x, cursor_y, mouse_c, index, chain_list, generate_timer, delete_timer, year
    ###############
    # タイトル
    ###############
    if index == 0:
        draw_txt("スタート", 312, 560, 50, "light green", "TITLE")
        # マウスボタン押下
        if mouse_c == 1:
            mouse_c = 0
            cvs.delete("TITLE")
            index = 1
        # カーソルの描画(非表示)
        cvs.delete("CURSOR")
        # ボールの描画
        cvs.delete("BALL")
        draw_ball()

    ###############
    # ゲーム中
    ###############
    elif index == 1:
        # ボール生成処理
        if generate_timer > 0:
            generate_timer -= 1
        elif generate_timer == 0:
            # 生成時間0秒:ボール生成
            for x in range(8):
                # 生成箇所にボールがあった場合ゲームオーバー
                if not ball[0][x] == 0:
                    index = 2
                    break
                if random.randint(1, 2) == 1:
                    ball[0][x] = random.randint(1, 4)
            # 次の生成時間を設定(25秒)
            generate_timer = 20
        # ボール削除処理
        if delete_timer > 0:
            delete_timer -= 1
            # 削除時間5秒:「祝」に変換
            if delete_timer == 5:
                # 最下段3段を全て「祝」に変換
                for y in range(9, 6, -1):
                    for x in range(8):
                        if ball[y][x] in (1, 2, 3, 4):
                            ball[y][x] = 9
        elif delete_timer == 0:
            # 削除時間0秒:ボール削除
            for y in range(10):
                for x in range(8):
                    # 「謹賀新年」「祝」を削除
                    if ball[y][x] in (5, 6, 7, 8, 9):
                        ball[y][x] = 0
        # マウスがステージ内の場合
        if 24 <= mouse_x < 24+72*8 and 24 <= mouse_y < 24+72*10:
            cursor_x = int((mouse_x-24)/72)
            cursor_y = int((mouse_y-24)/72)
            # マウスボタン押下時
            if mouse_c == 1:
                # ボールを繋いでいない時、または次のボールに繋いだ時
                max_chain = len(chain_list)
                if max_chain == 0 or chain_list[max_chain-1][0] != cursor_y or chain_list[max_chain-1][1] != cursor_x:
                    # 繋いでる文字の次の文字の場合
                    if ball[cursor_y][cursor_x] == max_chain + 1:
                        if ball[cursor_y][cursor_x] == 4:
                            # あけおめを繋いだら「謹賀新年」に変換
                            chain_list.append([cursor_y, cursor_x])
                            for item in chain_list:
                                ball[item[0]][item[1]] = ball[item[0]][item[1]] + 4
                            # チェインリストクリア
                            chain_list = []
                            mouse_c = 0
                            # 削除までの時間を設定(15秒)
                            delete_timer = 15
                            # 年度を加算
                            year += 1
                        else:
                            # あけおめを繋げる
                            chain_list.append([cursor_y, cursor_x])
                    else:
                        # あけおめの文字以外に繋いだ場合
                        # チェインリストクリア
                        chain_list = []
                        mouse_c = 0
            else:
                # マウスボタン離した場合
                # チェインリストクリア
                chain_list = []
        # カーソルの描画
        cvs.delete("CURSOR")
        cvs.create_image(cursor_x*72+60, cursor_y*72+60, image=cursor, tag="CURSOR")
        # ボールの描画
        drop_ball()
        cvs.delete("BALL")
        draw_ball()

    ###############
    # ゲームオーバー
    ###############
    elif index == 2:
        draw_txt("ゲームオーバー", 312, 560, 50, "purple", "TITLE")
        # マウスボタン押下
        if mouse_c == 1:
            mouse_c = 0
            cvs.delete("TITLE")
            index = 0
            # ステージとスコアを初期化
            year = 2020
            init_stage()
    # 年度の描画
    cvs.delete("YEAR")
    draw_txt("年度:" + str(year), 135, 60, 32, "blue", "YEAR")
    # 0.1秒後に再実行
    root.after(100, game_main)


root = tkinter.Tk()
root.title("あけおめパズル")
root.resizable(False, False)
# マウスイベントの登録
root.bind("<Motion>", mouse_move)
root.bind("<ButtonPress>", mouse_press)
# Canvasの描画
cvs = tkinter.Canvas(root, width=912, height=768)
cvs.pack()
# 画像の取得
bg = tkinter.PhotoImage(file="ball_bg.png")
cursor = tkinter.PhotoImage(file="ball_cursor.png")
img_ball = [
    None,
    tkinter.PhotoImage(file="ball_1.png"),
    tkinter.PhotoImage(file="ball_2.png"),
    tkinter.PhotoImage(file="ball_3.png"),
    tkinter.PhotoImage(file="ball_4.png"),
    tkinter.PhotoImage(file="ball_gold_1.png"),
    tkinter.PhotoImage(file="ball_gold_2.png"),
    tkinter.PhotoImage(file="ball_gold_3.png"),
    tkinter.PhotoImage(file="ball_gold_4.png"),
    tkinter.PhotoImage(file="ball_gold_5.png")
]
cvs.create_image(456, 384, image=bg)
# ステージ生成
init_stage()
# メイン処理
game_main()
root.mainloop()
エレキベア
エレキベア
長くて分からないクマ
マイケル
マイケル
細かくみていきます!
マウス操作の検知について
マイケル
マイケル
マウスの操作については、下記のように
「<Motion>」「<ButtonPress>」をbind することによって検知しています。
検知後は各変数に値を設定します。

###############
# マウス操作用
###############
cursor_x = 0
cursor_y = 0
mouse_x = 0
mouse_y = 0
mouse_c = 0


# マウス移動処理
def mouse_move(e):
    global mouse_x, mouse_y
    mouse_x = e.x
    mouse_y = e.y


# マウスクリック処理
def mouse_press(e):
    global mouse_c
    mouse_c = 1

・・・略・・・

# マウスイベントの登録
root.bind("<Motion>", mouse_move)
root.bind("<ButtonPress>", mouse_press)

・・・略・・・

↑マウス操作の検知
ゲームメイン処理について
マイケル
マイケル
ゲームのメイン処理についてはroot.after()メソッドに時間を設定することによってループ処理を行なっています。
下記の例では0.1秒毎にgame_main()メソッドを実行しています。

# ゲームメイン処理
def game_main():

・・・略・・・

    # 0.1秒後に再実行
    root.after(100, game_main)


↑ゲームのメイン処理
エレキベア
エレキベア
UnityでいうUpdateメソッドの代わりクマね
画面の状態切り替えについて
マイケル
マイケル
画面の状態については、index変数に「タイトル画面」「ゲーム中」「ゲームオーバー」の状態を設定することで処理を分けています。

###############
# ゲーム内変数
###############
index = 0

・・・略・・・

# ゲームメイン処理
def game_main():
    global cursor_x, cursor_y, mouse_c, index, chain_list, generate_timer, delete_timer, year
    ###############
    # タイトル
    ###############
    if index == 0:

・・・略・・・

    ###############
    # ゲーム中
    ###############
    elif index == 1:

・・・略・・・

    ###############
    # ゲームオーバー
    ###############
    elif index == 2:

・・・略・・・

エレキベア
エレキベア
ループ処理の中で各処理を分岐しているクマね
ボールの描画と落下処理
マイケル
マイケル
ここからゲームの処理内容に入っていきます。
ステージは 横8列 * 縦10列の配列 として定義し、
draw_ball()メソッド で描画しています。

# ステージの定義
def init_stage():
    global ball
    ball = [
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
    ]


# ボール描画処理
def draw_ball():
    for y in range(10):
        for x in range(8):
            if ball[y][x] > 0:
                cvs.create_image(x*72+60, y*72+60, image=img_ball[ball[y][x]], tag="BALL")


# ボール落下処理
def drop_ball():
    # 下から確認、最下段は除く
    for y in range(8, -1, -1):
        for x in range(8):
            if ball[y][x] != 0 and ball[y+1][x] == 0:
                ball[y+1][x] = ball[y][x]
                ball[y][x] = 0

・・・略・・・

# ゲームメイン処理
def game_main():

・・・略・・・

    ###############
    # ゲーム中
    ###############
    elif index == 1:

・・・略・・・

        # ボールの描画
        drop_ball()
        cvs.delete("BALL")
        draw_ball()

・・・略・・・

img_ball = [
    None,
    tkinter.PhotoImage(file="ball_1.png"),
    tkinter.PhotoImage(file="ball_2.png"),
    tkinter.PhotoImage(file="ball_3.png"),
    tkinter.PhotoImage(file="ball_4.png"),
    tkinter.PhotoImage(file="ball_gold_1.png"),
    tkinter.PhotoImage(file="ball_gold_2.png"),
    tkinter.PhotoImage(file="ball_gold_3.png"),
    tkinter.PhotoImage(file="ball_gold_4.png"),
    tkinter.PhotoImage(file="ball_gold_5.png")
]

・・・略・・・

マイケル
マイケル
各ボールの画像は img_ball変数 に定義していて、
配列0番目はNone(何も表示しない)にしています。


【img_ball配列の設定値】
1〜4:「あ」「け」「お」「め」のボール
5〜8:「謹」「賀」「新」「年」のボール
9:  「祝」のボール

エレキベア
エレキベア
ステージ配列内の数字を変えることで各ボールが表示されるクマね
マイケル
マイケル
そういうことだね!
そして描画したボールは drop_ball()メソッド で徐々に落下するよう実装しています。
ボール生成処理
マイケル
マイケル
ボールの生成処理は下記箇所で行なっています!
ループ時間とは別に generate_timer変数 を用意し、一定時間ごとにランダムで生成するよう実装しています。

generate_timer = 0

・・・略・・・

    ###############
    # ゲーム中
    ###############
    elif index == 1:
        # ボール生成処理
        if generate_timer > 0:
            generate_timer -= 1
        elif generate_timer == 0:
            # 生成時間0秒:ボール生成
            for x in range(8):
                # 生成箇所にボールがあった場合ゲームオーバー
                if not ball[0][x] == 0:
                    index = 2
                    break
                if random.randint(1, 2) == 1:
                    ball[0][x] = random.randint(1, 4)
            # 次の生成時間を設定(25秒)
            generate_timer = 20


・・・略・・・

↑ボール生成処理
エレキベア
エレキベア
ここで「あ」「け」「お」「め」のボールを生成するクマね
ボールを繋げる処理
マイケル
マイケル
そしてあけおめの文字を繋げる判定処理は下記部分です!
ボタンが押されている状態でカーソルが移動した場合に
あけおめの順番で繋いでいるかチェックし、
正しい場合はchain_list変数に格納
しています!

###############
# ゲーム内変数
###############
index = 0
chain_list = []
generate_timer = 0
delete_timer = 0
ball = []
year = 2020

・・・略・・・

        # マウスがステージ内の場合
        if 24 <= mouse_x < 24+72*8 and 24 <= mouse_y < 24+72*10:
            cursor_x = int((mouse_x-24)/72)
            cursor_y = int((mouse_y-24)/72)
            # マウスボタン押下時
            if mouse_c == 1:
                # ボールを繋いでいない時、または次のボールに繋いだ時
                max_chain = len(chain_list)
                if max_chain == 0 or chain_list[max_chain-1][0] != cursor_y or chain_list[max_chain-1][1] != cursor_x:
                    # 繋いでる文字の次の文字の場合
                    if ball[cursor_y][cursor_x] == max_chain + 1:
                        if ball[cursor_y][cursor_x] == 4:
                            # あけおめを繋いだら「謹賀新年」に変換
                            chain_list.append([cursor_y, cursor_x])
                            for item in chain_list:
                                ball[item[0]][item[1]] = ball[item[0]][item[1]] + 4
                            # チェインリストクリア
                            chain_list = []
                            mouse_c = 0
                            # 削除までの時間を設定(15秒)
                            delete_timer = 15
                            # 年度を加算
                            year += 1
                        else:
                            # あけおめを繋げる
                            chain_list.append([cursor_y, cursor_x])
                    else:
                        # あけおめの文字以外に繋いだ場合
                        # チェインリストクリア
                        chain_list = []
                        mouse_c = 0
            else:
                # マウスボタン離した場合
                # チェインリストクリア
                chain_list = []

↑ボールを繋げる処理
エレキベア
エレキベア
ここの処理が肝クマね
「あけおめ」まで繋げ終わったら「謹賀新年」に変える処理 も行なっているクマね
ボール削除処理
マイケル
マイケル
そして最後にボール削除処理!
ボールを繋げる処理部分で設定された delete_timer変数をカウントダウンし、
削除の5秒前になったら最下段3段全て「祝」ボールに変えています。
マイケル
マイケル
あとは削除時間0秒になった時点で「謹賀新年」「祝」のボールを全て削除するよう実装すれば完了です!

delete_timer = 0

・・・略・・・

        # ボール削除処理
        if delete_timer > 0:
            delete_timer -= 1
            # 削除時間5秒:「祝」に変換
            if delete_timer == 5:
                # 最下段3段を全て「祝」に変換
                for y in range(9, 6, -1):
                    for x in range(8):
                        if ball[y][x] in (1, 2, 3, 4):
                            ball[y][x] = 9
        elif delete_timer == 0:
            # 削除時間0秒:ボール削除
            for y in range(10):
                for x in range(8):
                    # 「謹賀新年」「祝」を削除
                    if ball[y][x] in (5, 6, 7, 8, 9):
                        ball[y][x] = 0
        # マウスがステージ内の場合
        if 24 <= mouse_x < 24+72*8 and 24 <= mouse_y < 24+72*10:

・・・略・・・

                            # 削除までの時間を設定(15秒)
                            delete_timer = 15

エレキベア
エレキベア
完成したクマ〜〜〜〜〜
スポンサーリンク

おわりに

マイケル
マイケル
駆け足でしたがざっと処理内容を解説しました!
どうだったかな??
エレキベア
エレキベア
簡単にそれらしいゲームができて楽しかったクマ〜〜
マイケル
マイケル
Pythonは気軽に開発できるのがいいよね
今回作ったゲームは難易度が一定なので、
気が向いた方はぜひレベルデザインにも挑戦してみてね!
エレキベア
エレキベア
作り込んでやるクマ〜〜〜〜
マイケル
マイケル
それでは今日はこの辺で!
のんびりしたお正月をお過ごしくださいね!
エレキベア
エレキベア
パズル作るクマ〜〜〜〜〜〜〜
マイケル
マイケル
お餅食べよ〜〜〜〜

【Python】ぷよぷよ風の文字連結パズルゲームを作る【あけおめパズル】 〜完〜

コメント