Unity6のWebGLにおけるランキング機能の実装手順

こんにちは、トモです。

今日はランキング機能をUnity6のWebGLビルドで実装したいと思います。

ランキング機能について参考にしたウェブサイト

まず、ランキング機能について検索したりChatGPTに尋ねたりしたところ、PHP・MySQLでデータベースをサーバー上に作成して、データベースにUnity側からアクセスするというような仕組みだという事が分かりました。

まったく意味が分からないので、グーグル先生に相談しました。

小一時間下のウェブサイトを読みふけって何となくわかってきました。

参考サイト:

▼XSERVER(エックスサーバー)でデータベースを作成する方法

▼【Unity】ランキング機能を自作してみたお話

▼【Unity】第一回 ソーシャルゲームを作る 〜サーバの環境構築〜【AWS × Laravel】

▼【自前DB】ニフクラ mobile backendがサ終するらしいのでオンラインランキング自作した話

▼PHPとは?簡単解説!初心者向けPHPの基本と仕組み

▼エックスサーバーで PHP は使える?現バージョンの確認とバージョンアップ方法について

▼Xserver:MySQLの設定

▼【初心者向け】phpMyAdminとは?インストール方法、使い方までわかりやすく解説

▼今からでも遅くない! phpMyAdminインストールから使い方まで徹底解説

ランキング機能を作る手順

上の記事を読む限り、どうやら、下記の順序でやる必要がありそうでした。


・サーバーを用意する必要がある

・そのサーバー上にSQLデータベースを作る

・そのデータベースに書き込んだりするPHPスクリプトを書いてそのサーバーにアップする

・Unity側ではそのサーバー上のPHPにアクセスして情報を送る

まず、私はこのウェブサイトのサーバーをXサーバーで借りているので、それを使う事に決めました。

まず、ChatGPTに「Xserverを使用しているので、サーバー側で必要な手続きを教えて下さい」と聞きました。すると、以下が返ってきました。

Xserverでランキング機能を実装するためのサーバー設定について、以下の手順を確認してみてください。

PHPやデータベースの設定

Xserverでは、MySQLデータベースやPHPが利用できるので、ランキングデータを保存するデータベースを作成し、PHPスクリプトを使ってデータの送受信を行います。

データベース設定

必要に応じてユーザーとパスワードも設定しておきます。

Xserverの管理画面にログインし、「MySQL設定」から新規データベースを作成します。

Xサーバーで新規データベースを作る

XサーバーにはMySQL設定という設定がウェブサイトのサーバー管理画面にあります。

このウェブサイトが管理されているWordPress利用中のデータベースとは別のデータベースを新たに作成しました。

そして、ユーザーを作成しパスワードを設定して、アクセス権所有ユーザに指定します。

これで一応データベースができたという事になるようです。実体が見えないので実感は湧きません。

XサーバーのphpMyAdminでデータベース内にテーブルを作成する

XサーバーにはデフォルトでphpMyAdminというデータベースを管理する機能(?)が付いているようです。

サーバー管理画面にリンクがあるのでそれをクリックすると、ユーザー名とパスワードを訊かれるので、入力します。

ログインできるとこのような画面になります。

作成したtomodesignst_rankingというデータベースを選択して4カラムのテーブルを作成します。

最後に保存するを押すまで保存されないので注意が必要です。

id はオートインクリメントA.I.というところをクリックします。

chatGPTに相談して下図のように設定しました。

これでMySQL(?)データベース側の準備ができました。

PHPファイルをサーバー上に置く

Xサーバーには初期ドメインというのがあり、無料でデフォルトで付いてくるドメインがあります。

自力でムームードメイン等で取得したドメインはこのウェブサイトの管理等に使っているので、未使用の初期ドメイン上にPHPファイルを置いてみることにしました。

PHPファイルを公開したいディレクトリにアップロードします。通常は public_html フォルダ内が公開ディレクトリになります。

実は、この二つのPHPファイルを置く場所が分かるまでにかなりの試行錯誤を繰り返しました。

PHPファイルの中身は以下です。

post_score.php(スコア送信用)

<?php
$servername = "localhost";
$username = "your_db_user";
$password = "your_db_password";
$dbname = "your_db_name";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name = $_POST["name"];
    $score = $_POST["score"];

    $stmt = $conn->prepare("INSERT INTO ranking (name, score) VALUES (?, ?)");
    $stmt->bind_param("si", $name, $score);
    $stmt->execute();
    $stmt->close();
    echo "Score saved successfully!";
} else {
    echo "Invalid request method.";
}

$conn->close();
?>

get_scores.php(スコア取得用)

<?php
$servername = "localhost";
$username = "your_db_user";
$password = "your_db_password";
$dbname = "your_db_name";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT name, score FROM ranking ORDER BY score DESC LIMIT 10";
$result = $conn->query($sql);

$scores = array();
while ($row = $result->fetch_assoc()) {
    $scores[] = $row;
}

header('Content-Type: application/json');
echo json_encode($scores);

$conn->close();
?>

スクリプト内の以下の部分を、自分のデータベース設定に合わせて変更します。

$servername = "localhost";
$username = "your_db_user";  // MySQLのユーザー名
$password = "your_db_password";  // MySQLのパスワード
$dbname = "your_db_name";  // 使用するデータベース名

これらのファイルをtxtエディタで書いてxxxxx.php.txtと一回保存して public_html にアップします。

アップ後に、リネームして.textの部分を消すとPHPファイルとして認識されます。

とりあえず、これでファイルのアップロードはOKでした。

PHPファイルにアクセスする

レンタルサーバー名がxxxxxxxとすると、以下で例えば片方のPHPファイルにアクセスできるはずだそうです。

  • https://xxxxxxx.xsrv.jp/ranking/post_score.php

クロームでやってみましたがダメでした。

色々調べた結果、どうやら、ファイルのパーミッションの関係の可能性が浮上しました。

chatGPTは以下のように回答しています。

FTPクライアントやXserverのファイルマネージャーを使って、post_score.phpのパーミッションを確認してください。一般的には、644に設定することで、外部からアクセス可能な状態になります。

以下のようになっていたので、644に変更しました。

グループの読み取りが可能になったようなのですが、最終的に色々試しましたが、PHPファイルにブラウザからアクセスすることはできませんでした。

PHPファイルにエラーがないか確認するためのコードも足してみました。chatGPTに訊いて以下のようにしてエラーが出るように設定してみましたが、結局アクセスできませんでした。

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

// 以下に既存のコードを追加

?>

ここで、この問題を放置してとりあえずUnity側からPHPファイルにアクセスできないか挑戦しました。


Unity側からPHPファイルにアクセスを試みる

chatGPTによる手順

  1. Unityプロジェクトに新しいC#スクリプトを作成します。名前は RankingTest.cs などにしておきます。
  2. 以下のコードをスクリプトに追加します。
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class RankingTest : MonoBehaviour
{
    private string postUrl = "https://xxxxxxx.xsrv.jp/ranking/post_score.php";

    // テストデータ
    private string playerName = "TestPlayer";
    private int score = 1000;

    // ボタンをクリックするとスコアを送信
    public void SendScore()
    {
        StartCoroutine(PostScore(playerName, score));
    }

    // POSTリクエストを送信するコルーチン
    private IEnumerator PostScore(string name, int score)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", name);
        form.AddField("score", score);

        using (UnityWebRequest www = UnityWebRequest.Post(postUrl, form))
        {
            yield return www.SendWebRequest();

            if (www.result == UnityWebRequest.Result.Success)
            {
                Debug.Log("Response: " + www.downloadHandler.text);
            }
            else
            {
                Debug.LogError("Error: " + www.error);
            }
        }
    }
}
  1. スクリプトをUnityシーンに配置
    • 上記のスクリプトをアタッチするために、Unityシーン内に空のGameObject(例えば「RankingTester」)を作成し、RankingTestスクリプトをそのGameObjectにアタッチします。
  2. ボタンでスコア送信テスト
    • Unityエディターで、UIボタンを作成し、ボタンのOnClickイベントに RankingTest スクリプトの SendScore メソッドを設定します。
    • ゲームを再生し、ボタンをクリックするとスコアが送信され、サーバーからの応答が Debug.Log に表示されます。

これをUnityのWebGLビルドでやった結果、以下のようなエラーが得られました。



意味が分からなかったのでこれもchatGPTに訊きます。

Curl error 60: Cert verify failed. Certificate Common Name(CN) does not match with the expected CN. UnityTls error code: 7 Error: SSL CA certificate error UnityEngine.Debug:LogError (object) RankingTest/<PostScore>d__4:MoveNext () (at Assets/Scripts/RankingTest.cs:36) UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)

すると、Xサーバーの「無料独自SSL設定」が可能な場合は、それを有効化して、正しいSSL証明書を適用すれば上記のエラーが解消する可能性があることがわかりました。

OFFになっていた無料独自SSL設定をONに変えます。これは反映待ちが消えるまでに1時間きっかりかかりました。

WebGL特有のCORS設定

その間にいろいろ調べたのですが、WebGLビルドの場合は、下のコードをPHPに足す必要があるという事が分かりました。

// CORS設定 

header("Access-Control-Allow-Origin: *"); 
header("Access-Control-Allow-Methods: POST, GET"); 
header("Content-Type: application/json; charset=UTF-8");

説明(chatGPT)

  • *header("Access-Control-Allow-Origin: ");
    すべてのオリジンからのリクエストを許可します(必要に応じて特定のオリジンに変更可能です)。
  • header("Access-Control-Allow-Methods: POST, GET");
    POSTとGETリクエストのみ許可します。必要に応じて他のHTTPメソッドを追加できます。
  • header("Content-Type: application/json; charset=UTF-8");
    応答のContent-TypeをJSON形式として設定し、文字コードをUTF-8にします。

これをphpのかっこの中の冒頭部分に加えました。

WebGL特有のCORS設定とSSL設定の反映後にUnityで通信を試みる

設定反映後、やっと通信に成功しました。

phpMyAdmin側にもデータが来ていることが確認できました。

また、POSTだけでなくGETの方も試してみるとうまくいきました。

よく見るとphpMyAdmin側の時間データが0になっています。設定後に保存しなかったようなので、タイプをtimestampに、デフォルト値を current_timestampに設定し直します。

時間も記録できるようになりました。

InputFieldを使った任意の名前とスコアの送信

InputFieldに任意の名前とスコアを入れてデータベースに送信できるようにします。

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using TMPro;

public class RankingInputTest : MonoBehaviour
{
    private string postUrl = "https://tomodesignst.xsrv.jp/ranking/post_score.php";

    // UI要素
    public TMP_InputField nameInputField;
    public TMP_InputField scoreInputField;

    // ボタンをクリックするとスコアを送信
    public void SendScore()
    {
        string playerName = nameInputField.text;
        int score;

        // スコアが整数値か確認
        if (int.TryParse(scoreInputField.text, out score))
        {
            StartCoroutine(PostScore(playerName, score));
        }
        else
        {
            Debug.LogError("Error: スコアは数値で入力してください。");
        }
    }

    // POSTリクエストを送信するコルーチン
    private IEnumerator PostScore(string name, int score)
    {
        WWWForm form = new WWWForm();
        form.AddField("name", name);
        form.AddField("score", score);

        using (UnityWebRequest www = UnityWebRequest.Post(postUrl, form))
        {
            yield return www.SendWebRequest();

            if (www.result == UnityWebRequest.Result.Success)
            {
                Debug.Log("Response: " + www.downloadHandler.text);
            }
            else
            {
                Debug.LogError("Error: " + www.error);
            }
        }
    }
}

説明:

  1. UI要素の取得: nameInputFieldscoreInputFieldを使用して、ユーザーが入力した名前とスコアを取得します。
  2. 入力の検証: int.TryParseでスコアが整数として入力されているか確認します。スコアが数値でない場合はエラーメッセージを表示します。
  3. スコアの送信: 入力された名前とスコアをPostScoreコルーチンに渡し、サーバーに送信します。

Unityでの設定:

  1. RankingTestスクリプトをCanvas内の適切なGameObjectにアタッチします。
  2. nameInputFieldscoreInputFieldにそれぞれ名前とスコア入力用のInputFieldを割り当てます。
  3. ButtonのクリックイベントにRankingTest.SendScore関数を設定します。

注)inputfieldオブジェクトを選択し、inspectorからLineTypeをMultiLineNewlineに変更しています。

データベースをフェッチしてランキングを表示する

以下のコードをデータベースから上位のランキングをフェッチするために書きました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;

public class RankingTest2 : MonoBehaviour
{
    private string getUrl = "https://tomodesignst.xsrv.jp/ranking/get_scores.php";

    // UI要素
    public GameObject scoreContainer;  // ランキングを表示するコンテナ
    public TMP_Text scoreTemplate;     // スコアを表示するテンプレートのTMP_Textオブジェクト

    public void FetchScores()
    {
        StartCoroutine(GetScores());
    }

    private IEnumerator GetScores()
    {
        using (UnityWebRequest www = UnityWebRequest.Get(getUrl))
        {
            yield return www.SendWebRequest();

            if (www.result == UnityWebRequest.Result.Success)
            {
                // 既存の表示をクリア
                foreach (Transform child in scoreContainer.transform)
                {
                    Destroy(child.gameObject);
                }

                // JSONデータをリストとしてパースして表示する
                List<ScoreEntry> scores = JsonUtility.FromJson<ScoreList>("{\"scores\":" + www.downloadHandler.text + "}").scores;
                foreach (var score in scores)
                {
                    // TMP_Textオブジェクトを作成してコンテナに配置
                    TMP_Text scoreText = Instantiate(scoreTemplate, scoreContainer.transform);
                    scoreText.text = "Name: " + score.name + ", Score: " + score.score;
                    scoreText.gameObject.SetActive(true); // テンプレートが非表示の場合、表示を有効にする
                }
            }
            else
            {
                Debug.LogError("Error: " + www.error);
            }
        }
    }

    // JSONを受け取るためのクラス
    [System.Serializable]
    public class ScoreEntry
    {
        public string name;
        public int score;
    }

    [System.Serializable]
    public class ScoreList
    {
        public List<ScoreEntry> scores;
    }
}

ScoreTemplateを用意する

scoreTemplate は、ランキングの各項目(名前とスコア)を表示するための テンプレートオブジェクト です。このテンプレートオブジェクトを複製して、ランキングデータを画面に表示するために使用します。

具体的な役割と作成方法

  1. 役割:
    • scoreTemplateは、取得したランキングデータ(名前とスコア)を表示するための「ひな型」として機能します。データの数だけこのテンプレートを複製して、それぞれの項目に対応したテキスト内容を設定し、画面に表示します。
  2. 作成方法:
    • UnityのCanvas内に、TextMesh ProのText (TMP) コンポーネントを追加し、それをscoreTemplateとして使います。
    • 例えば、TextMesh Proのテキストオブジェクトを作成し、そのテキストの内容やフォント、スタイルなどを設定します。次に、このオブジェクトをスクリプトのscoreTemplateとして設定します。
    • このscoreTemplateオブジェクトを非表示(SetActive(false))にしておき、ランキングデータを受け取ったときに複製して使用します。
  3. スクリプト内での利用:
    • スクリプト内でscoreTemplateを複製(Instantiate)し、各ランキング項目の名前とスコアを設定します。複製されたオブジェクトはscoreContainerに配置され、ランキングリストが表示されます。

例: Unity内での設定手順

  1. Canvasの作成: Canvas内に、ランキングを表示するためのTextMesh Proのテキストオブジェクトを配置します。
  2. scoreTemplateオブジェクトの設定: TextMesh Proのテキストオブジェクトの名前を scoreTemplate に設定し、最初は非表示にしておきます。
  3. スクリプトへの割り当て: スクリプトの scoreTemplate フィールドに、この TextMesh Proのテキストオブジェクトを割り当てます。

これで、スクリプト内で scoreTemplate を使ってランキングを画面に表示する準備が整います。

ScoreContainerを用意する

scoreContainer は、ランキング項目をまとめて表示するためのコンテナオブジェクトです。これは、複数のランキング項目(名前とスコアの表示)を整理して表示するために使われる 親オブジェクト です。

具体的な役割と作成方法

  1. 役割:
    • scoreContainerは、各ランキング項目(scoreTemplateを複製したオブジェクト)をまとめて格納する親オブジェクトです。
    • scoreContainer内にランキング項目を追加することで、UnityのVertical Layout GroupContent Size Fitterと組み合わせて、ランキングを縦方向に整然と並べて表示することができます。
  2. 作成方法:
    • Canvasの中に空のGameObjectを作成し、これをscoreContainerとして使います。
    • scoreContainerにはVertical Layout Groupコンポーネントを追加しておくと、複製されたランキング項目が自動的に縦に並んで表示されます。
    • scoreContainerに、Content Size Fitterコンポーネントを追加して、コンテナのサイズを自動調整することもできます(Vertical Layout Groupと組み合わせると便利です)。
  3. スクリプト内での利用:
    • スクリプトで、scoreContainer内にscoreTemplateを複製したオブジェクトを追加していきます。これにより、ランキング項目が一箇所にまとめられ、きれいに整列して表示されます。

例: Unity内での設定手順

  1. Canvasの作成: Canvas内に、ランキングを表示するための空のGameObjectを作成します。このGameObjectscoreContainerとして設定します。
  2. Vertical Layout Groupの追加: scoreContainerに対して、Vertical Layout Groupコンポーネントを追加します。これにより、scoreContainer内に追加されるオブジェクトが縦に並べられます。
  3. スクリプトへの割り当て: スクリプトの scoreContainer フィールドに、この空のGameObjectを割り当てます。

これで、スクリプト内でランキング項目をscoreContainerに追加することで、画面にランキングリストが整然と表示されるようになります。

おわりに

これで一通りスコアの送信とランキングの表示ができました。

かなりChatGPTに助けられたところが多いですが、割とシンプルな実装ができたかと思います。


それでは、また!

おすすめの記事