Skip to main content

持っているカードを確認

カードの表示方法を理解する

Unityで「CardTest」シーンを開く。以下のような画面が表示される。

image.png

このシーンを使って、カード表示機能をテストできる。使用する値は次の通りである。

  • ランク:0〜4の値で、それぞれC、B、A、S、SSに対応する
  • イラスト番号:中央のイラストを選択するための0〜9の値
  • モンスター名:表示するモンスターの名前
  • 攻撃力、防衛力、体力:0〜99の攻撃力、防衛力、体力の値

これらはすべて CardDisplay クラスによって処理される。特に、以下に示す SetData メソッドで扱われる。

// データを設定し、カードを更新
public void SetData(CardDisplayData data)
{
    // スプライトを読み込む
    cardRank.sprite = CardResources.GetRankSprite(data.rank);
    monsterPicture.sprite = CardResources.GetMonsterSprite(data.pictureID);
    
    // テキストを更新
    monsterName.text = data.monsterName;
    atkValue.text = data.atkValue.ToString();
    defValue.text = data.defValue.ToString();
    hpValue.text = data.hpValue.ToString();
}

ここで、CardDisplayData はカードを更新するために必要なデータである。

public struct CardDisplayData
{
    public int id;             // カードのID
    public int rank;           // ランク(0:C -> 4:SS)
    public int pictureID;      // 画像番号 (0~9)
    public string monsterName; // モンスター名
    public int atkValue;       // 攻撃力 
    public int defValue;       // 防衛力
    public int hpValue;        // 体力
}

データベースを作成する

全カードの一覧

この知識を使って、ゲーム内の全カードを保存するための新しいテーブルをデータベースに作成しよう。phpMyAdmin をもう一度開き、以下のように必要なカラムを持つ「cards」という名前のテーブルを作成しよう。

image.png

そして、データベースにいくつかカードを追加しよう。

image.png

以下のSQL文を使ってカードを挿入してもいいし、自分で作ってもよい。

INSERT INTO `cards` (`id`, `rank`, `picture_id`, `monster_name`, `attack`, `defense`, `hit_points`) VALUES
(1, 0, 0, '弱虫スケルトン', 3, 1, 10),
(2, 0, 1, '黒オニ', 4, 1, 8),
(3, 0, 0, 'ホネボネ', 3, 2, 15),
(4, 0, 1, '悪魔赤ちゃん', 5, 2, 10),
(5, 1, 4, 'ファイヤー猫', 8, 2, 12),
(6, 1, 3, 'スカルゴースト', 6, 5, 10),
(7, 1, 8, 'アルミ戦士', 4, 8, 10),
(8, 2, 5, 'カエルる', 10, 5, 20),
(9, 2, 6, 'メデゥーサ', 8, 8, 15),
(10, 3, 7, '木マン', 5, 15, 22),
(11, 3, 8, '闇ナイト', 12, 15, 12),
(12, 4, 9, 'ギャラクシー', 20, 12, 35);

プレイヤーが所有するカードの一覧

プレイヤーごとに持っているカードが異なるため、「users」テーブルと「cards」テーブルの間に関係を作る必要がある。これは、この2つを単純な形でつなぐ「user_cards」という新しいテーブルを作成することで実現できる。

image.png

見ての通り、このテーブルにはレコードID、ユーザーID、カードID、そのカードの所持枚数という4つのカラムしかない。テストユーザーとカードはすでに用意してあるので、それぞれのプレイヤーにカードを持たせよう。手動で追加してもいいし、以下のSQL文を使って手早く初期カードを用意してもよい。

image.png

INSERT INTO `user_cards` (`id`, `user_id`, `card_id`, `count`) VALUES
(1, 1, 1, 2), (2, 1, 2, 1), (3, 1, 5, 1),(4, 1, 8, 2),
(5, 1, 6, 1), (6, 2, 3, 5), (7, 2, 6, 3),(8, 2, 12, 1);

APIを実装する

テーブルとテストデータが揃った。次は、プレイヤーが所有するカードを返すPHPスクリプトを作る。Unity側のコードは詳細なカード情報を必要とするため、ランク、イラスト番号、モンスター名、各種数値などすべてを送る必要がある。ただし、データを重複させたくないので、カードの枚数も一緒に送ることにする。最終的なJSONは次のような形になる

{
  "playerCards" : [
    {
      "card" : {
        "id" : 1,
        "rank" : 0, 
        "pictureID" : 0, 
        "monsterName" : "弱虫スケルトン", 
        "atkValue" : 3, 
        "defValue" : 1, 
        "hpValue" : 10
      },
      "count" : 2
    },
    ...
  ]
}

PHP スクリプト

まず、データベースにクエリを送る。パラメータとしてユーザーIDを受け取り、そのユーザーが持っているすべてのカードを検索する。カードを持っていない場合や、ユーザーが存在しない場合は、空の配列を返す。

テスト用として、ユーザーIDはGETパラメータで渡し、最終版ではPOSTに変更する。get_player_cards.php ファイルを作成し、以下のように実装しよう。

<?php
	// JSONを返すべき
	header("Content-Type: application/json; charset=utf-8");

	// 接続 
	$dbh = new PDO('mysql:host=localhost; dbname=card_game', "root", "", [PDO::ATTR_PERSISTENT => true]);
	
	// ユーザーIDを取得
	$user_id = $_GET['user_id'];
	
	// そのユーザーを持っているカードを検索
	$sql = sprintf("SELECT * FROM user_cards WHERE user_id = '%s'", $user_id);
	$result = $dbh->query($sql);
	
	// 返すデータを準備する
	$json["playerCards"] = [];
	
	while ($record = $result->fetch())
	{
		// カード1枚に関するデータ
		$card_data['card']['id'] = $record['card_id'];
		$card_data['count']      = $record['count'];
		
		// 結果に追加
		array_push($json["playerCards"], $card_data);
	}

	print(json_encode($json));
?>

ここでは、ユーザーIDを受け取り、そのユーザーが持っているすべてのカードを検索して返している。しかし、以下のテストからわかるように、

SELECT * FROM user_cards, cards WHERE user_id = 1 AND user_cards.card_id = cards.id;

image.png

これはカードの詳細情報ではなく、カードのIDを返しているだけである。Unityで必要なデータを取得するには、2つのテーブルを結合して同時に検索する必要がある。これはJOIN操作と呼ばれる。まずは phpMyAdmin でテストしてみよう。以下のSQLクエリから始める。

SELECT * FROM user_cards WHERE user_id = 1;

image.png

これはPHPの結果と同じである。次に、cardsテーブルを結合し、card_idがcardsテーブルの同じidと一致するという条件を追加しよう。つまり、

SELECT * FROM user_cards, cards WHERE user_id = 1 AND user_cards.card_id = cards.id;

image.png

完璧である!これで、user_id = 1 が持つすべてのカードについて、詳細な情報が得られた。改良したSQLクエリを使うようにPHPスクリプトを修正し、必要な結果を返すようにしよう。

<?php
	// JSONを返すべき
	header("Content-Type: application/json; charset=utf-8");

	// 接続 
	$dbh = new PDO('mysql:host=localhost; dbname=card_game', "root", "", [PDO::ATTR_PERSISTENT => true]);
	
	// ユーザーIDを取得
	$user_id = $_GET['user_id'];
	
	// そのユーザーを持っているカードを検索
	$sql = sprintf("SELECT * FROM user_cards, cards WHERE user_id = '%s' AND user_cards.card_id = cards.id", $user_id);
	$result = $dbh->query($sql);
	
	// 返すデータを準備する
	$json["playerCards"] = [];
	
	while ($record = $result->fetch())
	{
		// カード1枚に関するデータ(Unityの構造体に合わせる)
		$card_data['card']['id']          = $record['id'];
		$card_data['card']['rank']        = $record['rank'];
		$card_data['card']['pictureID']   = $record['picture_id'];
		$card_data['card']['monsterName'] = $record['monster_name'];
		$card_data['card']['atkValue']    = $record['attack'];
		$card_data['card']['defValue']    = $record['defense'];
		$card_data['card']['hpValue']     = $record['hit_points'];
		
		$card_data['count'] = $record['count'];
		
		// 結果に追加
		array_push($json["playerCards"], $card_data);
	}

	print(json_encode($json));
?>

もう一度テストを実行すると、以下のようになる。

image.png

これは、求めていたJSON形式である。あとはスクリプト内の $_GET を $_POST に変更すれば、次のステップに進む準備は完了である。

Unityを使い、APIとの接続

APIRequest クラスを更新し、get_player_cards.php にアクセスして、使える形に変換する新しいメソッドを追加しよう。まず、必要な構造体を追加する。

// 所有カードを求める結果
[Serializable]
public struct PlayerCard
{
    public CardDisplayData card;
    public int count;
}

[Serializable]
public struct PlayerCardResponse
{
    public PlayerCard[] playerCards;
}

続いて、リクエスト用のメソッドを追加する。

// プレーヤーが所有しているカードを求める
// userID:ユーザーID
// 戻り値:PlayerCardのリスト
public static async Awaitable<PlayerCardResponse> GetPlayerCards(string userID)
{
    // POSTでデータを準備
    var postData = new WWWForm();
    postData.AddField("user_id", userID);

    // APIのURLへリクエストを送信
    const string url = ServerAPI + "/get_player_cards.php";
    var webRequest = UnityWebRequest.Post(url, postData);

    // 返事を待機する
    await webRequest.SendWebRequest();

    // 返事の文字列を取得
    var webResponse = webRequest.downloadHandler.text;

    // JSONからPlayerCardの配列へ変換
    PlayerCardResponse response = JsonUtility.FromJson<PlayerCardResponse>(webResponse);

    // 返す
    return response;
}

自分のユーザーで試してみる

ユーザーIDは LoginController.UserID に保存してあるので、サーバーに対してプレイヤーの全カードを取得するようリクエストできる。まずは CardListController を見てみよう。

// カード一覧を管理する
public class CardListController : MonoBehaviour
{
// 画面のアニメーション
[SerializeField] private ScreenSlide screenSlide;

// 戻るボタン
[SerializeField] private Button backButton;

// 戻るボタン
[SerializeField] private Button deleteButton;

// カード一覧のグリッド
[SerializeField] private GridLayoutGroup cardsList;

// カードのプレハブ
[SerializeField] private CardDisplay cardPrefab;

// 画面を表示する
public void Show()
{
    screenSlide.Show();
    _ = LoadCardsAsync();
}

// 選択されたカードを削除
private void DeleteSelectedCards()
{
    // 未実装
}

// カードを読み込み、画面に追加し、表示する
private async Awaitable LoadCardsAsync()
{
    // すべてのカード一を削除する
    foreach (Transform child in cardsList.transform)
        Destroy(child.gameObject);

    await Awaitable.NextFrameAsync();

    // オンライン機能を実装する前、いくつかのカード手動に追加
    var cards = new CardDisplayData[]
    {
        new()
        {
            rank = 0, pictureID = 0,
            monsterName = "テストモンスターA",
            atkValue = 10, defValue = 10, hpValue = 10
        },
        new()
        {
            rank = 0, pictureID = 1,
            monsterName = "テストモンスターB",
            atkValue = 20, defValue = 20, hpValue = 20
        },
        new()
        {
            rank = 1, pictureID = 2,
            monsterName = "テストモンスターC",
            atkValue = 20, defValue = 20, hpValue = 20
        },
        new()
        {
            rank = 1, pictureID = 2,
            monsterName = "テストモンスターC",
            atkValue = 20, defValue = 20, hpValue = 20
        },
    };

    foreach (var card in cards)
    {
        var copy = Instantiate(cardPrefab, cardsList.transform);
        copy.SetData(card);
        copy.SetSelectable(true);
    }
}

private void OnEnable()
{
    backButton.onClick.AddListener(()=>screenSlide.Hide());
    deleteButton.onClick.AddListener(DeleteSelectedCards);
}

private void OnDisable()
{
    backButton.onClick.RemoveAllListeners();
}

これは非常にシンプルなスクリプトである。画面に表示されているカードをすべて消してから、テスト用のカードを読み込んでいる。「削除」ボタンは現時点では何もせず、「戻る」ボタンはトップメニューに戻る。

APIを使うには、オフラインのテストカードを置き換えて、サーバーにカードをリクエストするだけでよい。APIRequest メソッドはすでに用意してあるので、ここで使ってみよう。

// カードを読み込み、画面に追加し、表示する
private async Awaitable LoadCardsAsync()
{
    // すべてのカード一を削除する
    foreach (Transform child in cardsList.transform)
        Destroy(child.gameObject);
    
    // サーバーにカードの一覧を要求
    var cards = await APIRequest.GetPlayerCards(LoginController.UserID);

    // 1枚ずつを追加
    foreach (var item in cards.playerCards)
    {
        for (var i = 0; i < item.count; i++)
        {
            var copy = Instantiate(cardPrefab, cardsList.transform);
            copy.SetData(item.card);
            copy.SetSelectable(true);
        }
    }
}

もう一度ログインして、プレイヤーのカードを読み込んでみよう。

image.png

これは、データベースに保存しておいたカードである!

image.png