Skip to main content

ガチャを引く

カードを一覧表示して、削除もできるようになった。次は、ガチャゲームを作ってカードを追加していこう。このゲームには、次の機能が必要になる。

  • プレイヤーがガチャを引ける回数には制限がある
  • カードのレア度は、カードのランクと連動している(SSカードはCカードより出にくい)

具体的には、各レア度の確率は次の通りである。

Rank SS S A B C
Rarity (%) 5% 10% 20% 30% 35%

データベース

まず、各プレイヤーが持つガチャの回数を保存するために、データベースに新しいテーブルを作る。必要なのは、ユーザーIDと回数だけである。

image.png

まず、各プレイヤーが持つガチャの回数を保存するために、データベースに新しいテーブルを作る。必要なのは、ユーザーIDと回数だけである。

INSERT INTO `gacha` (`user_id`, `count`) VALUES ('1', '3');

image.png

PHP スクリプト

ガチャを引く回数を求める

新しいAPIが2つ必要になる。1つはガチャの残り回数を確認するもの、もう1つは実際にガチャを引くものである。まずは簡単な方から始めよう。get_gacha_count.phpという新しいスクリプトを作る。これは単純な SELECT 文で、プレイヤーの残り回数を調べるだけである。テーブルにレコードが無い場合は、0を返す。

いつも通り、まずは GET パラメータでテストしてみよう。調べたい user_id を引数として渡す。

<?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を取得
	$userID = $_GET'user_id'];
	
	// SQL文を構築
	$sql = "SELECT * FROM gacha WHERE user_id = " . $userID;
	
	// デフォルトとして 0 を返す
	$json['count'] = 0;
	
	// 見つけたら、回数を取得
	$result = $dbh->query($sql);
	if ($result->rowCount() == 1)
	{
		$record = $result->fetch();
		$json['count'] = $record['count'];
	}
	
	// 返す
	print(json_encode($json));
?>

user_id=1とuser_id=3でテストすると、次のような結果が得られる。

image.png

image.png

これが求めていた正しい出力である。次のステップに進む前に、$_GET$_POST に変更するのを忘れないでください。

ガチャを引く

次のスクリプトは、ガチャを実際に引く処理を行う。いくつかの処理が必要になるので、少し複雑である。

  • SSからCまでの中から、ランダムにカードのランクを決める
  • 決まったランクのカードを、すべてデータベースから取得する
    • その中から、ランダムに1枚を選ぶ
  • 選んだカードを、プレイヤーの持っているカード一覧に追加する
  • ガチャの残り回数を1減らす
  • 新しく手に入れたカードの情報を、JSONでゲーム(Unity)側に返す

この処理は、4つの関数に分けて順番に呼び出すことができる。まずは、ランダムにランクを決める処理から始めよう。gacha_pull.php という新しいスクリプトを作ってください。

確率でランダムにカードのランクを決める

// 確率によりランダムランクを作成
// 0:C ~ 4:SS
function createRandomRank()
{
	// 0~100までの乱数を生成
	$chance = rand(0, 100)
	
	// 5% 以下だったら、SSランク(4)のカードになる
	if ($chance < 5) 
		return 4; 
	
	// 15% 以下だったら、Sランク(3)のカードになる
	if ($chance < 15)
		return 3; 

	// 35% 以下だったら、Aランク(2)のカードになる
	if ($chance < 35)
		return 2; 

	// 65% 以下だったら、Bランク(1)のカードになる
	if ($chance < 65)
		return 1; 

	// 残りは C(0)
	return 0;
}

このスクリプトは、0から100までのランダムな数を生成し、累積確率を順番にチェックしながら、0(C) から 4(SS) までのランクを1つ見つけて返す。

ランクを指定してデータベースからランダムにカードを選ぶ

次のスクリプトは、0から4までのランク値を受け取り、データベースを検索して、その中からランダムに1枚のカードのレコードを返す。

// ランクを用いて、ランダムのカードを返す
// $dbh:  データベースを検索するためのPDO
// $rank: カードのランク
function getCardWithRank($dbh, $rank)
{
    // SQL文を構築
    $sql = "SELECT * FROM cards WHERE rank = " . $rank;
    
    // すべてのレコードを取得
    $result = $dbh->query($sql);
    $records = $result->fetchAll();
    
    // ランダムのを選択
    $count = sizeof($records);
    $idx   = rand(0, $count-1);
    return $records[$idx];
}

プレイヤーにカードを追加する

このステップでは、user_cards テーブルを更新して、プレイヤーの持っているカードを反映させる。もしプレイヤーがすでにそのカードを持っていたら、count に1を足す必要がある。逆に、まだ持っていなければ、count を1として新しいレコードを追加する必要がある。幸い、SQL には INSERT ... ON DUPLICATE という特別な文がある。読むのは少し複雑だが、だいたい次のような形になる。

INSERT INTO table (カラム1, カラム2, ...) VALUES (値1, 値2, ..) 
ON DUPLICATE KEY UPDATE カラム1 = 値1, カラム2 = 値2, ...

この場合、重複するレコードが見つかったら、新しく追加する代わりに更新される。ただし、そのためには、重複してはいけないフィールドを「UNIQUE」として指定しておく必要がある。続ける前に、phpMyAdmin を開いて user_cards テーブルを編集してみよう。

image.png

user_idcard_id の両方を選択して、「UNIQUE」に設定してください。こうすることで、user_idcard_id の組み合わせが重複するレコードは存在できないと、データベースに伝えることができる。例えば、

user_id = 3, card_id = 1

は、

user_id = 3, card_id = 2

user_id = 2, card_id = 1

と共存できる。少なくともどちらかのフィールドが違うからである。しかし、user_id = 3, card_id = 1というレコードをもう一度追加しようとすると、その組み合わせは一意でなければならないため、エラーになりデータベースには追加されない。この2つの列の組み合わせが一意であることは、次の画面で確認できる。

image.png

一意なフィールドができたので、特別な INSERT ... ON DUPLICATE 文を使って、ユーザーとカードの組み合わせが存在するかどうかによって、追加または更新を行うことができる。だいたい次のような形になる。

INSERT INTO user_cards (user_id, card_id, count) VALUES ($userID, $cardID, 1) 
ON DUPLICATE KEY UPDATE count = count + 1

この文は、新しいuser_id / card_idの組み合わせを、count = 1として追加しようとする。もし重複していたら、代わりにcountを1増やす。これで、次のようなPHP関数が書けるようになる。

// ユーザーにカードを追加する
// $dbh:    データベースを検索するためのPDO
// $userID: ユーザーID
// $cardID: カードID
function addCardToPlayer($dbh, $userID, $cardID)
{
    // SQL文を構築
    $sql = "INSERT INTO user_cards (user_id, card_id, count) "
         . "VALUES ($userID, $cardID, 1) "
         . "ON DUPLICATE KEY UPDATE count = count + 1";
         
    // 実行
    $result = $dbh->exec($sql);
}

ガチャの回数を減らす

カードをプレイヤーに追加できたので、次はガチャの残り回数を減らす。これは、単純な UPDATE 文で実現できる。

// ガチャの数を減らす
// $dbh:    データベースを検索するためのPDO
// $userID: ユーザーID
function decreaseGachaPullCount($dbh, $userID)
{
    // SQL文を構築
    $sql = "UPDATE gacha SET count = count - 1 "
         . "WHERE user_id = " . $userID;
         
    // 実行
    $result = $dbh->exec($sql);
}

まとめる

あとは、これまで作った関数をすべて実行して、手に入れたカードの情報をJSONでプレイヤーに返すだけである。

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

// 接続 
$dbh = new PDO('mysql:host=localhost; dbname=card_game', "root", "", [PDO::ATTR_PERSISTENT => true]);

// ユーザーID
$userID = $_POST['user_id'];

$rank = createRandomRank();
$card = getCardWithRank($dbh, $rank);
addCardToPlayer($dbh, $userID, $card['id']);
decreaseGachaPullCount($dbh, $userID);

// カード情報を返す
$json['card']['id']           = $card['id'];
$json['card']['rank']         = $card['rank'];
$json['card']['picture_id']   = $card['picture_id'];
$json['card']['monster_name'] = $card['monster_name'];
$json['card']['atk_value']    = $card['attack'];
$json['card']['def_value']    = $card['defense'];
$json['card']['hp_value']     = $card['hit_points'];	

print(json_encode($json));

スクリプト全体はこちらである。

Unityを使い、APIとの接続

次は、Unity側の作業に移る。まず、それぞれのPHP関数に対応するAPIリクエストを2つ作ろう。

APIRequest

ガチャの回数取得

// ガチャの回数を返す
// userID:ユーザーID
public static async Awaitable<int> GetGachaCountAsync(string userID)
{
    // POSTでデータを準備
    var postData = new WWWForm();
    postData.AddField("user_id", userID);

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

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

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

    // JSONからPlayerCardの配列へ変換
    JObject response = JObject.Parse(webResponse);

    // 返す
    return (int)response["count"];
}

ガチャを引く

// ガチャを引く
// userID:ユーザーID
public static async Awaitable<JObject> GachaPullAsync(string userID)
{
    // POSTでデータを準備
    var postData = new WWWForm();
    postData.AddField("user_id", userID);

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

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

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

    // JSONからPlayerCardの配列へ変換
    JObject response = JObject.Parse(webResponse);

    // 返す
    return (JObject)response["card"];
}

GachaController

ガチャの回数

次に、GachaController.cs を更新する必要がある。このスクリプトは今のところアニメーションを再生するだけで、サーバーとはつながっていない。APIを使って残り回数を確認し、その回数分だけガチャを引けるように更新していこう。

UpdateGachaCountAsync() の中にある、次の行を置き換えてください。

// 未実装:サーバーに依頼し、数を取得
await Awaitable.NextFrameAsync();
int count = 1; // とりあえず固定

APIの呼び出しに置き換える。

// サーバーに依頼し、数を取得
int count = await APIRequest.GetGachaCountAsync(LoginController.UserID);

Unity上で試してみて、データベースで設定した通り「3」回が正しく取得できるか確認してください。

ガチャを引く

最後に、ガチャを引くAPIを GachaController とつなげて、結果をカード表示に反映させる必要がある。APIが返すカード情報は、CardDisplay が期待している形式とちょうど同じなので、わりと簡単につなげることができる。

PullGachaAsync() の中にある、次の行を置き換えてください。

// 未実装:サーバーに依頼し、ガチャを引き、
//        カードを更新する
await Awaitable.NextFrameAsync();

次のように置き換える。

//サーバーに依頼し、ガチャを引き、カードを更新する
JObject data = await APIRequest.GachaPullAsync(LoginController.UserID);
card.SetData(data);

あとは、実際にガチャゲームを遊ぶだけである!回数が無くなるまで3回引いてみて、それからカード一覧に戻って、新しいカードが増えているか確認してください!