前回の 🤖 プロジェクトに Game Center プラグインを追加する の記事では Godot 公式の iOS プラグインのうち Game Center のプラグインをプロジェクトに追加する手順について説明したんやけど、今回の記事では、その追加したプラグインを使って、実際に Apple の Game Center の機能を実装する方法について書いてるで。

そもそも Game Center って何?って人がいるかもしれんけど、前回の記事の冒頭で軽く説明してるから、よかったら先にそっち見てみてな。

おおまかには、大きく次の2つのセクションに分けて説明していくことにするわ。

  1. App Store Connect で Game Center の設定をする
  2. GDScript で Game Center の実装をする

ちなみに、Godot Docs / iOS plugins / iOS用プラグイン にプラグインの使い方の基本は書いてあるから、まずそこを見てもらうのが順当な感じするわ。

あと、Game Center プラグインのコードについては GitHub のほうにある Godot iOS GameCenter plugin で確認できるから、細かく確認したい人はこっちも見てみて。個人的には最初ちょっと難易度高めやなと思ったけどね。

記事作成当時の筆者の環境
Godot のバージョン: 4.2.1
コンピュータのモデル: MacBook Air M1, 2020
OS: macOS 14.4.1



Xcode から App Store Connect にビルドをアップロードする

まず大前提として、Godot から Export したプロジェクトを Xcode から App Store Connect にアップロードする必要があるねん。具体的な手順は、別の記事 🤖 Xcode から App Store Connect にゲームを配布する を参考にしてみてな。Xcode で Game Center 機能を有効にする手順も説明してるで。


App Store Connect で Game Center の設定をする

まずは App Store Connect にアクセスして、Game Center と連携させたいアプリを選択したら、サイドバーの Game Center から Game Center の編集画面を開いてみよか。そしたら、そこで Leaderboard と 達成項目 を追加できそうな雰囲気漂ってんねん。一目瞭然やね。

ちなみに、下のスクリーンショットは僕がすでに Leaderboard と 達成項目 を追加した状態のものやから、最初はどっちも空っぽになってるで。
App Store Connect の Game Center 編集画面


Leaderboard の設定をする

Leaderboard っていうのは、世界中の同じゲームのプレイヤー同士が何かしらのスコアを Apple の Game Center を通して競い合える機能やねんな。この Leaderboard を使いたかったら、次の手順でやったらええで。

  1. まず Leaderboard の (+) ボタンをクリックする。
    Leaderboard の追加ボタン
  2. 参照名 と Leaderboard ID を決める。どっちがどっちかわからなくならんように同じにしといたらええわ。
    Leaderboard の追加ボタン
  3. 標準Leaderboard か 周期Leaderboard どっちにするか選択。前者はその Leaderboard を削除するまで永遠にスコアが残るタイプ。後者は周期的にリセットするタイプ。周期はあとで自分で設定するで。
    Leaderboard の追加ボタン
  4. スコアのフォーマット、スコア範囲、スコア送信タイプ、並び順をそれぞれ決めて。一番わかりやすいのは、フォーマットを整数にして、スコア範囲を最小値 0 から余裕持たせた最大値にすることやね。スコアが高い方が良いなら降順にして高いスコアが上にくるようにすればいいね。
    Leaderboard の追加ボタン

こんな感じで、必要な分だけ追加していくだけ。簡単やろ。例えば、最短クリア時間とか倒した敵の数とか、他のプレイヤーと競いたくなるようなスコアを設定してあげると、プレイヤーもモチベーションあがると思うねん。いろいろ考えて思いつくままに追加してってもええと思うよ。順番はいつでも自由に変更できるからね。
Leaderboard の順序を編集ボタン

Leaderboard の順序を編集パネル


達成項目の設定をする

達成項目 っていうのは、ゲームをプレイしていく中で、何かしらの目標を設定して、それを達成できたことを証明する機能やねん。例えば、ゲームをクリアするという最終ゴールに対して、中ボスを倒すごとに達成項目を用意したり、キャラクターの成長に合わせて達成項目を用意したりして、マイルストーンを置いてあげると、最後までゲームをプレイしてもらうのに役立ったりするねん。あとは、アイテム収集に関する達成項目とか、倒した敵の数なんかの達成項目とかのやり込み要素を追加すると、ゲームをより長く深く楽しんでもらえるかもしれんね。

達成項目を追加したいときは、次の手順に沿ってやってみてな。

  1. まずは 達成項目 の (+) ボタンをクリックする。
    達成項目追加手順1
  2. 開いたパネルで 参照名 と 達成項目ID を入力してから 作成 ボタンをクリックしてな。これもどっちがどっちかわからんようになると思う人は同じにしといたらいいんとちゃうかな。
    達成項目追加手順2
    💡 すでに作成済みの達成項目もその参照名のリンク踏んだら次の編集画面にいけるで。
    達成項目追加手順3
  3. 編集画面が開いたら、まずは「点数」「非表示」「複数回達成可能」を順番に設定してあげてな。設定項目の意味がわからんときは (?) ボタンをクリックしてみたらええよ。複数の達成項目を作る予定やったら、点数は合計1,000点を分配しなあかんから、割り算してうまいこと割り振ってね。
    達成項目追加手順4
  4. つづいて、ローカリゼーションを追加 ボタンを押して、ゲームに対応する言語ごとの設定をしていくで。
    達成項目追加手順5
    💡 すでに一つ以上ローカリゼーションを追加済みの場合は、こっちの (+) ボタンで追加してな。
    達成項目追加手順6
  5. こんな感じで各入力欄を埋めて、あとはせっかくやからその達成項目のためのイメージもアップロードしとこ。イメージなしでもいけるけど、それはさすがに素っ気ないやん?ただし、アップロードするイメージには制限があって、以下のように書いてあるから、画像を用意するときは注意してな。
    『達成項目を表すローカライズされた画像。画像は、1024x1024ピクセル、72dip以上、およびRGB色空間の.jpeg、.jpg、または.pngファイルであるひつようがあります。』
    言語ごとに異なるイメージを用意する必要はないから、あくまで達成項目に対して一つイメージが用意できたらいい感じやね。
    達成項目追加手順7
  6. ローカリゼーションが追加できたらこんな感じになるで。
    達成項目追加手順8

達成項目としてありそうなのは、例えば「クリア回数」、「プレイ時間」、「入手したレアアイテム」、「解放したスキル」とかかな。色々考えて追加してみてな。ただ、先に達成項目を全部洗い出して、点数の割り振りを決めてから作業したほうがええと個人的には思うよ。なんでかっていうと、あとから達成項目を追加するとなると、点数を割り振り直さなあかんから、けっこうめんどくさいのよ。



Game Center プラグインを使ってコーディングする

ほな、ここからは GDscript で Game Center 機能をコーディングするとこ説明していくで。

まずはプロジェクトの中に、game_center.gd とかそれっぽい名前で一つスクリプトつくろか。ファイルシステムドックで右クリックから追加できるで。

プロジェクトにスクリプトを追加する

ゲームが始まるやいなやこのスクリプトを読み込むようにしたいよなあ。そういうときは、Godot の Project > Project Settings… > Autoload タブを開いて、作ったスクリプトを追加したらええねんで。これでゲーム開始したタイミングでこのスクリプトが読み込まれるわ。

Autoloadにスクリプトを追加する

次は肝心のスクリプトの中身やな。game_center.gd スクリプトに Game Center のプログラムを実装していくわけやけど、最初はこんな感じで Game Center プラグインを入れるための変数を、例えば game_center って名前にして、先に宣言しとこか。

extends Node

# Game Center plugin
var game_center = null

つぎに _ready() 関数の中に、ゲーム開始直後から実行しておきたいプラグインの取得と認証の部分を追加していこか。コードは次のような感じやで。

func _ready():
    if OS.get_name() == "iOS":
        if Engine.has_singleton("GameCenter"):
            print("Found GameCenter plugin")
            game_center = Engine.get_singleton("GameCenter")
            var res = game_center.authenticate()
            print("Authentication: ", res)
        else:
            print("There is no GameCenter game_center")
  1. まず、OS が iOS かどうかを判定するのに OS.get_name() で OS の名前を取得してチェックしてるわ。
  2. OS が iOS やったら、つぎは Engine.has_singleton("GameCenter") で GameCenter プラグインが、ちゃんとあるかどうかもチェックしてるで。
  3. ほんで、プラグインがみつかったら、Engine.get_singleton("GameCenter") で Game Center プラグインを取得して、それを変数 game_center にぶっ込むというわけや。
  4. そのあとそのまま認証までやってるんやけど、認証は Game Center プラグインに備え付けの authenticate() 関数呼び出すだけでええから簡単やね。この関数、認証の結果を返してくれるから res 変数に結果入れて、print() で出力したらデバッグしやすいんとちゃうかな。

ここまでが Game Center 機能を実装する上での下準備やね。つぎは、Game Center の Leaderboard と Achievements の機能の実装について、それぞれ順番に説明していくで。



Leaderboard を実装する

Leaderboard からいきましょか。めっちゃシンプルな例で説明していくで。

最初に Leaderboard を更新するための関数を定義するで。ここでは update_leaderboard() っていう関数にしとくわ。

func update_leaderboard(score:int, category:String):
    print("call update_leaderboard()")
    if not game_center:
        print("No Game Center plugin")
        return

    if not game_center.is_authenticated():
        print("not authenticated, authenticating again")
        game_center.authenticate()

    var data = {"score": score, "category": category}
    print("New Leaderboard data: ", data)
    var res = game_center.post_score(data)
    print("Update Leaderboard response: " + str(res))
  1. 関数の引数は、score:int と category:String の2つ設定してるで。
  2. まず game_center 変数が空っぽの null やったら関数はそこで終了させる。
  3. つぎに、認証ができてなかった場合はもう一回認証させるで。基本的には _ready() 実行時に認証完了してるはずやけどね。
  4. data 変数に、辞書型のデータを代入するで。この辞書型データは "score" と "category" という2つのキーをもってるんやけど、"category" には App Store Connect で自分が用意した Leaderboard の名前を String 型で指定するねん。ほんで、"score" のほうに int 型で実際のスコアを入れてあげるわけや。これで、どの Leaderboard にどれだけのスコアを反映するのか、っていうのを指示するデータができあがるんやね。ちなみに、このデータの形式は、このあと登場する Game Center プラグインに備え付けの post_score() 関数の引数に渡すデータとして決められてるものやから、適当にはできへんとこなんよ。
  5. そして、プラグインの post_score() の引数に data 変数を渡して呼び出すねん。これで Leaderboard のスコアを更新してくれるわ。更新の結果を戻り値で出してくれるから、これも print() で出力してデバッグできるようにするとええと思うよ。

関数 update_leaderboard() が定義できたので、あとは実際にゲームの中でスコアを反映するタイミングで、この関数を呼び出せばいいね。

var current_score : int = 0
var highscore : int = 0

func update_highscore(current_score: int):
    if high_score < current_score:
        high_score = current_score
        update_leaderboard(high_score, "highscore")

例えば、簡単なシューティングゲームで考えてみよ。

  1. 変数 current_score を宣言しといて、敵を撃ち落としたらこの値がどんどん加算されていくことにしよか。
  2. もうひとつ highscore って変数も宣言しとくて。こっちは、過去最高記録を保存する変数で、Leaderboard に反映するやつやで。
  3. あとは update_highscore って関数で以下のロジックをまとめといて、ゲームオーバーのタイミングでこの関数を呼び出したらうまいこといくんとちゃうかな。
    1. highscore 変数の値を current_score 変数の値と比較
    2. current_score のほうが大きかったら highscore を更新する
    3. ついでに Game Center の Leaderboard も更新する

このへんはもうゲームによって全然変わってくるから各自で頑張ろね。

あとは、ゲーム内で Game Center の画面に移動して、Leaderboard を確認したいよね。自分は何位なのか、順位は変わってるのか、そもそも誰か遊んでくれてるのか、って気になるからね。そのための関数は例えばつぎのようにしたらええかな。

func show_leaderboard():
    if not game_center:
        print("No Game Center plugin")
        return

    if not game_center.is_authenticated():
        print("not authenticated, authenticating again")
        game_center.authenticate()

    var param := {"view": "leaderboards", "leaderboard_name": "highscore"}
    var res = game_center.show_game_center(param)
    print("Show Leaderboard response: ", res)

Button ノードを押したときに上のような関数を呼び出して、Game Center の Leaderboard 画面に遷移させるわかりやすいんとちゃうかな。

  1. 関数の名前は show_leaderboard()、引数はなし
  2. game_center 変数が空っぽの null やったら関数はそこで終了させる。
  3. 認証ができてなかった場合はもう一回認証させるで。基本的には _ready() 実行時に認証完了してるはず。
  4. param 変数に辞書型データを入れる。これはプラグイン備え付けの show_game_center() 関数の引数で指定の形式。 "view" キーには "leaderboards" (複数形!)を、"leaderboard_name" キーには "highscore"(App Store Connect で設定した名前)をそれぞれ値として指定してな。
  5. param 変数を引数に渡しつつ、プラグインの show_game_center() 関数を呼び出す。戻り値で結果がわかるから、関数を変数 res に入れて、そのあとデバッグ用として print() で出力するようにしておく。


達成項目を実装する

つぎに 達成項目 に関わるプログラムをコーディングしていくで。といっても Leaderboard のほうで要領つかめてたら似たようなもんやから安心してな。こちらもシンプルな例でいこか。

func update_achievement(item_name:String, progress:float):
    print("called report_achievement()")
    if not game_center:
        print("No Game Center plugin")
        return

    if not game_center.is_authenticated():
        print("not authenticated, authenticating again")
        game_center.authenticate()

    var data = {"name": item_name, "progress": progress}
    print("New Achievement data: ", data)
    var res = game_center.award_achievement(data)
    print("Update Achievement response: " + str(res))
  1. 関数は、例えば update_achievement() という名前で、引数は、item_name:String と progress:float の2つ設定して定義するで。
  2. まず game_center 変数が空っぽの null やったら関数はそこで終了させる。
  3. つぎに、認証ができてなかった場合はもう一回認証させるで。基本的には _ready() 実行時に認証完了してるはずやね。
  4. data 変数に、辞書型のデータを代入するで。この辞書型データは "name" と "progress" という2つのキーをもってるんやけど、"name" には App Store Connect で自分が用意した達成項目の名前を String 型で指定するねん。ほんで、"progress" のほうに float 型でその達成項目についての達成率(単位は %)を渡してあげると。これで、どの達成項目の条件をどれくらい達成できているのかをを指示するデータができあがるねんな。ちなみに、この辞書型のデータ形式は、Game Center プラグインに備え付けの award_achievement() 関数の引数に渡すデータとして決められてるものやから、適当にやったらあかんのやね。
  5. そして、プラグインの award_achievement() を引数に data 変数を渡して呼び出すねん。これで指定した達成項目の達成率を更新してくれるわ。更新の結果を戻り値で出してくれるから、これも print() で出力してデバッグできるようにするとええと思うよ。

つづいて、実際に達成項目の達成率を更新する部分のコードも見てみよか。

var play_count := 0
var play_count_100 := false

func update_play_count_100():
    if not play_count_100:
        var progress = play_count / 100 * 100.0
        update_achievement("play_count_100", progress)
        if progress == 100.0:
            play_count_100 = true
  1. 例えば、100回プレイしたら達成という条件の達成項目があったとしようや。
  2. それに対して、プレイするたびにインクリメントされる変数 play_count: int を宣言しとくやん。
  3. そして、達成項目をクリア済みかどうかを示す変数 play_count_100: bool も必要やから宣言しとこな。
  4. ここで update_play_count_100() 関数を定義すんねん。プレイが始まるたびにこの関数を呼び出せば、達成項目の達成率が徐々に上がっていって、最終的に 100 % になるってわけやね。この関数の中には以下の内容を記述してな。
    1. で、if 文を使って play_count_100 が false(未達成)の場合だけ処理するように条件分岐させて。
    2. 変数 progress に、分母を 100、現在の play_count を分子にして、単位 % に合わせて 100 掛けたものを代入するで。これが現在の達成率になるわ。
    3. あとは、さっき作った関数 update_achievement() を、引数に "play_count_100" と progress 渡しつつ呼び出すねん。これで、Game Center の指定した達成項目が更新されるわ。
    4. あとは、progress が 100 やったら、今後は計測する必要がないので play_count_100 は true に更新しとこな。

達成項目の実装についてはこれくらいにしとくわ。



おわりに

ということで、今回は iOS 用のゲームで Game Center 機能を実装する方法について紹介したで。実は、Godot 公式の iOS プラグインのリポジトリにある Readme にどうやって実装するのか説明はあるんやけど、説明がちょっと少なめやったり、プラグインのコードがちょっと難解やったりで、個人的にも想定してたより実装に時間がかかったんよ。そういうわけで、この記事を補足資料として役立ててもらえたら嬉しい限りです。


ゲームの宣伝

最後に、実際に Game Center プラグインを使っている僕のゲームの宣伝だけさせてな。

👹 もの切り侍

今あなたの動体視力と反射神経が試される!侍を操作して、空から降ってくるあんな物やこんな物を見事空中で切ることができるか?!簡単片手操作でプレイできる、激ハマり必至の爽快カジュアル・タイミングゲーム!

🃏 ステキなソリティア

「ソリティア(クロンダイク)」と言えば、言わずと知れた定番中の定番、一人遊びカードゲームの王様なわけですが、このたび、モバイルゲーム「ステキなソリティア」が App Store で配信中!