kintoneGeeks blog

kintoneに関連する情報を発信しています

【Maker Faire Tokyo 2023 展示作品】射的2.0 🖥️GUI編

はじめに

Maker Faire Tokyo 2023に出展しました!
展示物のうちの「射的2.0」を、「🔫ピストル編」「🎯まと編」「⚙️システム編」「🖥️GUI編」の4本にわけてご紹介します。

この記事は「🖥️GUI編」になります。

ゲーム画面の実装

「射的2.0」はPythonで実装しました。
GUIにはPySimpleGUI、音楽や効果音にはPygameのmixer、HTTPリクエストにはRequestsを使っています。

GUIの作成 (PySimpleGUI)

GUIの構築に使用しているPySimpleGUIは、テキストボックスやボタン等を設置したウィンドウを構築することが出来るライブラリです。ユーザのインタラクションに対してのイベントを発火させる仕組みが備わっているので、とても使い勝手が良いです。

PySimpleGUIで生成したウィンドウの例

「射的2.0」ではゲームのタイトル画面、ランキング表示画面、ゲーム中の画面など、様々な異なる内容をウィンドウに表示する必要があります。各画面のレイアウト構造は一行ずつ配列の中で指定して定義します。例えばタイトル画面を作る関数は、下記のような構造でまとめました。

def make_TitlePage():
    TitlePageLayout = [
            [sg.Image("matoate_title_pic.png",expand_x=True,expand_y=True )],
            [sg.Push(),sg.Text("プレイヤー名を入力してください"),sg.Push()],
            [sg.Push(),sg.Input(key="-name_input-"),sg.Push()],
            [sg.Push(),sg.Button("ゲームスタート"),sg.Push()],
            [sg.Push(),sg.Button("ランキングを表示"),sg.Push()],
            [sg.Text("\n")]
            ]
    return sg.Window("Title Page", TitlePageLayout, finalize=True, size=window_size, location=window_location)

これをコード内で呼びます。

window = make_TitlePage()

そうするとウィンドウが生成され、画像、入力用のテキストボックスやボタン等を含めたコンテンツが生成されます。

「射的2.0」のタイトル画面

ちなみに配列内で数多く記載されているPush()は、『画面の端から反対方向まで詰める』という意味があり、表示するコンテンツの両側からPush()を指定することにより、コンテンツを中央に表示するようにしています。

音楽や効果音の実装 (Pygameのmixer)

表示しているページによって音楽や効果音を流したり止めたりする必要があったので、Pygameというライブラリを使いました。Pygameはゲーム作成に使うライブラリですが、その中のmixerのモジュールが音楽の扱いに長けていたので、今回使用することにしました。

使い方はシンプルで、まず設定の初期化をします。

mixer.init()
mixer.music.set_volume(0.1)

音楽を流したい箇所で、音楽ファイルを相対パスで指定してmixer.music.play()で実行します。すでに他の音楽が流れている可能性があるので、その前にmixer.music.pause()で流れている音楽を止められます。

mixer.music.pause()    #すでに流れている音楽を止める
mixer.music.load("sample.mp3") #同じディレクトリ内から"sample.mp3"を探して準備する
mixer.music.play()

射的2.0では下記のフリー素材を使用させて頂きました。ゲームを盛り上げてくれる最高のスパイスでした。ありがとうございました!

素材名 作者 リンク
MAY NOT! もっぴーさうんど https://dova-s.jp/bgm/play1421.html
Crazy Hill もっぴーさうんど https://dova-s.jp/bgm/play1398.html
Certain Victory ハモおた https://dova-s.jp/bgm/play17096.html
Funky droll street 蒲鉾さちこ https://dova-s.jp/bgm/play18703.html
警官のホイッスル2 効果音ラボ https://soundeffect-lab.info/
ユメイロドロップス えだまめ88 https://dova-s.jp/bgm/play18158.html
スタジアムの歓声 効果音ラボ https://soundeffect-lab.info/

HTTPリクエストの実装 (Requests)

今回はユーザのスコアの記録管理のために、kintoneアプリにデータが登録されるようにしました。また、ゲーム内ではこの溜まったスコアのデータをランキングで表示するようにしました。

kintoneではプレイヤー名を記録するための文字列一行フィールドと、スコアを記録するための数値フィールドが入ったアプリを準備し、認証用のAPIトークンを発行しました。

python内からkintoneへのデータの送受信を実装するために、Requestsライブラリを使用しました。

準備が出来たところで、kintoneアプリへのデータ登録用の関数と、kintoneからデータを取得するための関数を準備しました。次に紹介する関数では、例として下記の設定で進めます:

設定 備考
ドメイン sample.cybozu.com kintone環境はそれぞれ"cybozu.com"の前に固有の文字列が設定されています。
アプリID 50 アプリを作成した後に、URLにアプリIDの数値が記載されます
APIトークン X94FFop2333XcpxBBs225cDP リクエストの認証に使用する文字列で、アプリの設定画面から生成します
プレイヤー名のフィールドコード name アプリ内のフィールドの固有IDで、設定画面から変更できます
スコアのフィールドコード score 上と同じで、変更出来ます

kintoneアプリへのレコード登録

kintoneアプリにレコードを登録するために、1 件のレコードを登録するAPIのドキュメントを参考にします。エンドポイント、アプリID、APIトークンとボディのデータを指定します。

def addNameAndScoreToKintone(player_name, player_score):
    API_endpoint = "https://sample.cybozu.com/k/v1/record.json"
    app_id = "50"

    kintone_headers = {
        "X-Cybozu-API-Token": "X94FFop2333XcpxBBs225cDP",
        "Content-Type": "application/json"
    }

    bodydata = {
        "app": app_id,
        "record": {
            "name": {
                "value": player_name
            },
            "score": {
                "value": player_score
            }
        }
    }

    try:
        response = requests.post(API_endpoint, headers=kintone_headers, data=json.dumps(bodydata))
        jsondata = response.json()
        print(jsondata)
        return(jsondata["id"])

    except requests.exceptions.RequestException as error:
        print(error)

kintoneアプリからのレコード取得

kintoneアプリからレコードを複数件取得するために、複数のレコードを取得するAPIのドキュメントを参考にします。エンドポイント、アプリID、APIトークンとパラメータを指定します。

def get_RankingFromKintone():
    API_endpoint = "https://sample.cybozu.com/k/v1/records.json"
    app_id = "50"
    query = "date = TODAY() order by score desc limit 20"
    fields = "fields[0]=name&fields[1]=score&fields[2]=date&fields[3]=time&fields[4]=$id"
    API_endpoint = API_endpoint + "?app=" + app_id +"&query=" + query + "&" + fields

    kintone_headers = {
        "X-Cybozu-API-Token": "X94FFop2333XcpxBBs225cDP",
    }

    try:
        response = requests.get(API_endpoint, headers=kintone_headers)
        jsondata = response.json()
        table_data = []
        for index, record in enumerate(jsondata['records'], start=1):
            name = record['name']['value']
            score = record['score']['value']
            date = record['date']['value']
            time = record['time']['value']
            id = record['$id']['value']
            table_data.append([index, name, score, date, time, id])
        print(table_data)
        return (table_data)

    except requests.exceptions.RequestException as error:
        print(error)

queryには、『今日追加されたレコードをスコア値の降順で20件まで』という絞り込みを設定しました。そうすることで、ランキング表にリストする1位から20位までのプレイヤー名とスコアが取得出来ます。

query = "date = TODAY() order by score desc limit 20"

fieldsパラメータには、レスポンスに含めて欲しいフィールドを指定します。指定しない場合はレスポンスに全てのフィールドが返ってきてしまい、デバッグが難しくなるので、fieldsパラメータは設定することをお勧めします。このゲームでは、プレイヤー名、スコア、スコア登録された日にち、スコア登録された時間とレコードIDが取得されます。

fields = "fields[0]=name&fields[1]=score&fields[2]=date&fields[3]=time&fields[4]=$id"

PySimpleGUIで表を作りたいため、取得したデータは扱いやすい形に成形してからreturnします。

        for index, record in enumerate(jsondata['records'], start=1):
            name = record['name']['value']
            score = record['score']['value']
            date = record['date']['value']
            time = record['time']['value']
            id = record['$id']['value']
            table_data.append([index, name, score, date, time, id])
        print(table_data)
        return (table_data)

ランキングページ

このゲームではランキングページに移動したり、ゲームをクリアしてスコアを登録した後に、ランキング表が表示されるようにしています。先程紹介したレコード取得の関数を使って、returnされたデータでPySimpleGUIのTable()で表を作ります。

def make_RankingPage(row_to_highlight):
    ranking_list = get_RankingFromKintone()
    RankingPageLayout = [
            [sg.Table(
                      values=ranking_list, 
                      headings=['Rank','Name','Score',"Date","Time"],
                      col_widths=column_widths,
                      display_row_numbers=False,
                      justification='center',
                      num_rows=20,
                      row_colors=[(row_to_highlight, "green", "yellow")],
                      expand_x=True,
                      expand_y=True
                      )
            ],
            [sg.Push(),sg.Button("タイトルに戻る"),sg.Push()]
            ]
    return sg.Window("Ranking Page", RankingPageLayout, finalize=True, size=window_size, location=window_location)

PySimpleGUIの表は、特定の行を違う色で表示させることが出来ます。このゲームではランキングページに移動したら1位がハイライトされ、ゲームクリア後にランキング表を見る時には自分に関する行がハイライトされるようにしています(ランキング外だったらハイライトされない)。関数の引数として何行目をハイライトするかを決めてもらい、row_to_highlightで扱いました。

row_colors=[(row_to_highlight, "green", "yellow")]

ゲームクリア後に、ランキング内に入ったユーザの画面はこのように見えます。

kintoneのデータが入ったPySimpleGUIの表

最後に

細かいところでいうと画像や音楽をランダムに生成したり、ゲーム中のカウントダウンを実装したり、色々と案内したいところはありましたが、この記事ではここまでの案内とします。

ほとんどPySimpleGUIで実装した内容になりましたが、シンプルな画面の実装にはとても使いやすいライブラリなので、是非個人のプロジェクトでも使ってみてください。