こんにちは、トモです。
今日はランキング機能をUnity6のWebGLビルドで実装したいと思います。
ランキング機能について参考にしたウェブサイト
まず、ランキング機能について検索したりChatGPTに尋ねたりしたところ、PHP・MySQLでデータベースをサーバー上に作成して、データベースにUnity側からアクセスするというような仕組みだという事が分かりました。
まったく意味が分からないので、グーグル先生に相談しました。
小一時間下のウェブサイトを読みふけって何となくわかってきました。
参考サイト:
▼XSERVER(エックスサーバー)でデータベースを作成する方法
▼【Unity】第一回 ソーシャルゲームを作る 〜サーバの環境構築〜【AWS × Laravel】
▼【自前DB】ニフクラ mobile backendがサ終するらしいのでオンラインランキング自作した話
▼エックスサーバーで PHP は使える?現バージョンの確認とバージョンアップ方法について
▼【初心者向け】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による手順
- Unityプロジェクトに新しいC#スクリプトを作成します。名前は
RankingTest.cs
などにしておきます。- 以下のコードをスクリプトに追加します。
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); } } } }
- スクリプトをUnityシーンに配置
- 上記のスクリプトをアタッチするために、Unityシーン内に空のGameObject(例えば「RankingTester」)を作成し、
RankingTest
スクリプトをそのGameObjectにアタッチします。- ボタンでスコア送信テスト
- 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);
}
}
}
}
説明:
- UI要素の取得:
nameInputField
とscoreInputField
を使用して、ユーザーが入力した名前とスコアを取得します。 - 入力の検証:
int.TryParse
でスコアが整数として入力されているか確認します。スコアが数値でない場合はエラーメッセージを表示します。 - スコアの送信: 入力された名前とスコアを
PostScore
コルーチンに渡し、サーバーに送信します。
Unityでの設定:
RankingTest
スクリプトをCanvas内の適切なGameObjectにアタッチします。nameInputField
とscoreInputField
にそれぞれ名前とスコア入力用のInputField
を割り当てます。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
は、ランキングの各項目(名前とスコア)を表示するための テンプレートオブジェクト です。このテンプレートオブジェクトを複製して、ランキングデータを画面に表示するために使用します。
具体的な役割と作成方法
- 役割:
scoreTemplate
は、取得したランキングデータ(名前とスコア)を表示するための「ひな型」として機能します。データの数だけこのテンプレートを複製して、それぞれの項目に対応したテキスト内容を設定し、画面に表示します。
- 作成方法:
- UnityのCanvas内に、TextMesh ProのText (TMP) コンポーネントを追加し、それを
scoreTemplate
として使います。 - 例えば、TextMesh Proのテキストオブジェクトを作成し、そのテキストの内容やフォント、スタイルなどを設定します。次に、このオブジェクトをスクリプトの
scoreTemplate
として設定します。 - この
scoreTemplate
オブジェクトを非表示(SetActive(false)
)にしておき、ランキングデータを受け取ったときに複製して使用します。
- UnityのCanvas内に、TextMesh ProのText (TMP) コンポーネントを追加し、それを
- スクリプト内での利用:
- スクリプト内で
scoreTemplate
を複製(Instantiate
)し、各ランキング項目の名前とスコアを設定します。複製されたオブジェクトはscoreContainer
に配置され、ランキングリストが表示されます。
- スクリプト内で
例: Unity内での設定手順
- Canvasの作成: Canvas内に、ランキングを表示するためのTextMesh Proのテキストオブジェクトを配置します。
scoreTemplate
オブジェクトの設定: TextMesh Proのテキストオブジェクトの名前をscoreTemplate
に設定し、最初は非表示にしておきます。- スクリプトへの割り当て: スクリプトの
scoreTemplate
フィールドに、この TextMesh Proのテキストオブジェクトを割り当てます。
これで、スクリプト内で scoreTemplate
を使ってランキングを画面に表示する準備が整います。
ScoreContainerを用意する
scoreContainer
は、ランキング項目をまとめて表示するためのコンテナオブジェクトです。これは、複数のランキング項目(名前とスコアの表示)を整理して表示するために使われる 親オブジェクト です。
具体的な役割と作成方法
- 役割:
scoreContainer
は、各ランキング項目(scoreTemplate
を複製したオブジェクト)をまとめて格納する親オブジェクトです。scoreContainer
内にランキング項目を追加することで、UnityのVertical Layout GroupやContent Size Fitterと組み合わせて、ランキングを縦方向に整然と並べて表示することができます。
- 作成方法:
- Canvasの中に空の
GameObject
を作成し、これをscoreContainer
として使います。 scoreContainer
にはVertical Layout Groupコンポーネントを追加しておくと、複製されたランキング項目が自動的に縦に並んで表示されます。scoreContainer
に、Content Size Fitter
コンポーネントを追加して、コンテナのサイズを自動調整することもできます(Vertical Layout Group
と組み合わせると便利です)。
- Canvasの中に空の
- スクリプト内での利用:
- スクリプトで、
scoreContainer
内にscoreTemplate
を複製したオブジェクトを追加していきます。これにより、ランキング項目が一箇所にまとめられ、きれいに整列して表示されます。
- スクリプトで、
例: Unity内での設定手順
- Canvasの作成: Canvas内に、ランキングを表示するための空の
GameObject
を作成します。このGameObject
をscoreContainer
として設定します。 Vertical Layout Group
の追加:scoreContainer
に対して、Vertical Layout Group
コンポーネントを追加します。これにより、scoreContainer
内に追加されるオブジェクトが縦に並べられます。- スクリプトへの割り当て: スクリプトの
scoreContainer
フィールドに、この空のGameObject
を割り当てます。
これで、スクリプト内でランキング項目をscoreContainer
に追加することで、画面にランキングリストが整然と表示されるようになります。
おわりに
これで一通りスコアの送信とランキングの表示ができました。
かなりChatGPTに助けられたところが多いですが、割とシンプルな実装ができたかと思います。
それでは、また!