こんにちは、エンジニアのやまじです。
前編の記事から長らく時間が開いてしまいました、申し訳ありません…。
今回は前回予告していた通り、放置アプリの動作についてソースコードと動作している動画を交えて解説していきます。
放置アプリの大まかな解説
「状態取得 → 植木鉢にタネを植える → 成長 → 収穫 → 最初に戻る」という一連の流れを行います。
最後まで成長して育った果物を収穫することで、DB上の合計スコアを増やしていくという処理も実装しています。
収穫のたびにサーバー側のDBにスコアが蓄積されていくため、別のブラウザや端末からアクセスしてもデータが保持されたり、将来的にランキング機能を実装したりといった拡張も可能です。
ただ放置するだけでなく、自分のスコアが積み上がっていくことで、シンプルながらもちょっとした達成感が感じられるアプリを目指しています。
放置アプリを構成する5つのステップ
状態取得(GetState)
アプリ起動時、サーバーへ植木鉢の状態は現在どうなっているか確認します。

●ここでやっていること
Unity:GetState を実行し、サーバーへ現在の保存データ確認のためのリクエストを送信。
PHP:getState 関数が実行され、DBから現在の plant_state(植木鉢の状態)や planted_at(時刻)を取得して返す。
// GameAPI.cs(Unity側)
public IEnumerator GetState(string userId)
{
string url = ApiBaseUrl + "/game/getState";
GetStateRequest requestData = new GetStateRequest { userId = userId };
string json = JsonUtility.ToJson(requestData);
yield return PostRequest(url, json, HandleResponse);
}
// api.php(サーバー側)
function getState($data) {
$userId = $data['userId'] ?? '';
$conn = getDbConnection();
// ユーザーデータの存在確認・初期化
// ...中略...
// 最新のゲーム状態(成長度や残り時間)を取得して返却
$gameState = getGameStateByUserId($conn, $userId);
sendSuccessResponse($gameState);
$conn->close();
}
タネを植えて放置スタート
植木鉢に何も植えられていない状態で「タネを植える」ボタンを押すことで、放置アプリがスタートします。
●ここでやっていること
Unity:Plant を実行し、サーバー側にタネを植えて成長を開始するためのリクエストを送信。
PHP:plant 関数が実行され、サーバー側の現在時刻を planted_at としてDBに記録。植木鉢の状態(ステータス)を「タネの状態」に設定。
// GameAPI.cs(Unity側)
private IEnumerator Plant()
{
string url = ApiBaseUrl + "/game/plant";
ApiRequest requestData = new ApiRequest { userId = _currentUserId };
string json = JsonUtility.ToJson(requestData);
yield return PostRequest(url, json, HandleResponse);
}
// api.php(サーバー側)
function plant($data) {
$userId = $data['userId'] ?? 0;
$conn = getDbConnection();
$now = date('Y-m-d H:i:s');
// ステータスを1(タネ状態)にし、現在の時刻をタネを植えた時間として保存
$stmt = $conn->prepare("UPDATE game_states SET plant_state = 1, planted_at = ? WHERE user_id = ?");
$stmt->bind_param("si", $now, $userId);
$stmt->execute();
// ...
}
成長判定とグラフィックの変更
本アプリの核心部分です。アプリを閉じていても、サーバー側の時刻計算によって自動で成長が進みます。また、「様子を見る」ボタンを押すことで、画面上の残り時間が更新されます。
●ここでやっていること
Unity:updateGrowthState 内で「現在時刻 - 植えた時刻」から経過秒数を算出し、成長段階を判定・更新。
PHP:サーバーから届いた currentState に合わせ、switch 文で対応するスプライト(画像)に切り替え。
// GameAPI.cs(Unity側)
private void UpdateUI(GameStateResponse state)
{
int currentState = (state.plant != null) ? state.plant.currentState : 0;
Sprite targetSprite = sprite_Empty;
// ステータス番号に対応するスプライトをセット
switch (currentState)
{
case 1: targetSprite = sprite_Growth1; break; // 最初の芽
case 2: targetSprite = sprite_Growth2; break; // 少し成長
case 3: targetSprite = sprite_Growth3; break; // 花
case 4: targetSprite = sprite_HarvestReady; break; // 収穫可能
}
plantRenderer.sprite = targetSprite; // スプライトを差し替え
}
// api.php (サーバー側)
$elapsedSeconds = $now->getTimestamp() - $plantedTime->getTimestamp();
foreach ($growth_times as $stage => $requiredTime) {
if ($elapsedSeconds >= $requiredTime) {
$newState = $stage + 1; // 経過時間が設定した数値を超えていれば植木鉢を次の段階へ
}
}
収穫とスコアの記録
最後まで成長すると、植木鉢に生えていた植物は果物に変化。クリックすることで収穫できます。
●ここでやっていること
Unity:実った果実をクリックすることで HarvestPlant を実行。サーバーへ収穫のリクエストを送信。
PHP:harvest 関数内で現在の成長状態を最終チェック。クリックされたらDB上のフルーツ所持数をカウントアップ。
// GameAPI.cs(Unity側)
public void HarvestPlant() { StartCoroutine(Harvest()); }
private IEnumerator Harvest()
{
string url = ApiBaseUrl + "/game/harvest";
// ユーザーIDを含めたリクエストを作成
ApiRequest requestData = new ApiRequest { userId = _currentUserId };
string json = JsonUtility.ToJson(requestData);
// サーバーへ収穫リクエストをPOST送信
yield return PostRequest(url, json, HandleResponse);
}
// api.php(サーバー側)
function harvest($data) {
// ...中略...
// 成長状態を更新し、ステージ5(収穫可能)か判定
updateGrowthState($conn, $userId);
if ($currentState == 5) {
// フルーツを+1加算し、栽培データ(状態と時刻)をリセット
$stmt = $conn->prepare("UPDATE game_states SET plant_state = NULL, planted_at = NULL, fruits = fruits + 1 WHERE user_id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
}
}
最初の状態に戻る(リセット)
収穫が完了すると、植木鉢は自動で栽培前の状態へ戻り、再び遊べるようになります。
●ここでやっていること
PHP:収穫処理と同時に、DB上の plant_state と planted_at を NULL(空)に書き換えることで植木鉢の状態をリセット。
Unity:リセットされたデータ(plant が null)を受け取り、UIを「タネを植える」ボタンが表示される初期状態へ戻す。
// api.php (サーバー側)
// ステータスと時刻をNULLに更新して、鉢を空の状態にする
$stmt = $conn->prepare("UPDATE game_states SET plant_state = NULL, planted_at = NULL, fruits = fruits + 1 WHERE user_id = ?");
// GameAPI.cs (Unity側)
private void UpdateUI(GameStateResponse state)
{
// plantデータがNULL(またはcurrentStateが0)なら未植栽と判断
int currentState = (state.plant != null) ? state.plant.currentState : 0;
// 最新のフルーツ数を反映
fruitCountText.text = "これまで獲得したフルーツ: " + (state.inventory?.fruits.ToString() ?? "0");
// 状態が0なら「タネを植える」ボタンを再表示
plantButton.gameObject.SetActive(currentState == 0);
harvestButton.gameObject.SetActive(currentState == 4);
}
おわりに
3ヶ月越しの完結となりましたが、いかがでしたでしょうか。 時刻管理をサーバーに任せることで、アプリを閉じていても正確に時間が進む放置ゲームの基礎を構築できました。
「サーバーで時間を管理する」という手法は、放置ゲームだけでなく、スタミナ回復やログインボーナスなど、多くのオンライン要素が絡む技術でもあり、昨今のアプリ制作には欠かせない仕組みとなっています。
今後の課題としては、以下のような機能拡張を考えています。
- アイテムのバリエーションの追加: タネや果物の種類を増やす。
- 残り時間のリアルタイム表示: Unity側でサーバーとの差分を毎フレーム更新し、より滑らかなカウントダウンを実装。
- ユーザー登録機能を正式に実装し、複数ユーザーがそれぞれのデータで独立して遊べるよう改良する。
それでは。