サーバ時間を使ってスタミナを回復する

前回、FirebaseのCloud Functionを使ってサーバ時間を取得することに成功しました。

ローカル時間ではなくこのサーバ時間を使って、前々回作ったスタミナを回復する機能を調整してみようと思います。

と言うか結構作り変えました。

UI部分を別クラスへ

まず以前作ったものからUI部分だけを別クラス作ってそっちに移しました。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// スタミナのUI表示を扱うクラス
/// </summary>
public class StaminaUI : MonoBehaviour
{
    // StaminaControllerをInspectorから設定する
    [SerializeField]
    private StaminaController staminaController;

    // UIに表示する場合はInspectorから以下を設定してあげる
    [SerializeField]
    private Text staminaNumText; // スタミナの値を表示するテキスト
    [SerializeField]
    private Text restStaminaTimeText; // スタミナが1回復するまでの残り時間を表示するテキスト
    [SerializeField]
    private Slider staminaBarSlider; // スタミナの割合を表示するバー

    void Update()
    {
        // 各値を取得して
        int nowStaminaNum = UserData.Instance().nowStaminaNum;
        int maxStaminaNum = UserData.Instance().maxStaminaNum;
        bool isStaminaFull = nowStaminaNum >= maxStaminaNum;
        double restTime = staminaController.GetRestTime();

        // それぞれの表示を更新する
        if (staminaNumText != null) UpdateStaminaNum(nowStaminaNum, maxStaminaNum);
        if (restStaminaTimeText != null) UpdateRestStaminaTime(isStaminaFull, restTime);
        if (staminaBarSlider != null) UpdateStaminaBar(nowStaminaNum, maxStaminaNum);
    }

    /// <summary>
    /// スタミナの値を更新する
    /// </summary>
    /// <param name="nowStaminaNum">スタミナの現在値</param>
    /// <param name="maxStaminaNum">スタミナの最大値</param>
    private void UpdateStaminaNum(int nowStaminaNum, int maxStaminaNum)
    {
        staminaNumText.text = "" + nowStaminaNum + " / " + maxStaminaNum;
    }

    /// <summary>
    /// スタミナが回復する時間を更新する
    /// </summary>
    /// <param name="isStaminaFull">スタミナが最大かどうか</param>
    /// <param name="restTime">残り時間</param>
    private void UpdateRestStaminaTime(bool isStaminaFull, double restTime)
    {
        if(isStaminaFull)
        {
            restStaminaTimeText.text = "MAX";
        }
        else
        {
            int minutes = (int)restTime / 60;
            int seconds = (int)restTime % 60;
            restStaminaTimeText.text = "" + string.Format("{0:D2}", minutes) + ":" + string.Format("{0:D2}", seconds);
        }
    }

    /// <summary>
    /// スタミナバーを更新する
    /// </summary>
    /// <param name="nowStaminaNum">スタミナの現在値</param>
    /// <param name="maxStaminaNum">スタミナの最大値</param>
    private void UpdateStaminaBar(int nowStaminaNum, int maxStaminaNum)
    {
        float staminaPercentage = (float)nowStaminaNum / (float)maxStaminaNum;
        if (staminaPercentage >= 1) staminaPercentage = 1;
        staminaBarSlider.value = staminaPercentage;
    }
}

 

スタミナはユーザデータを管理するクラスへ

今後ユーザ関連のパラメータは一箇所で管理したいのでとりあえず分けておきました。

using UnityEngine;

using Const;

/// <summary>
/// ユーザデータを扱うクラス
/// </summary>
public class UserData
{
    private static UserData _Instance = new UserData();
    public static UserData Instance()
    {
        return _Instance;
    }

    public int maxStaminaNum;
    public int nowStaminaNum;

    /// <summary>
    /// 保存されたスタミナ値を読み込む
    /// </summary>
    public void LoadStamina()
    {
        // ユーザデータはサーバ側で管理したい……
        maxStaminaNum = PlayerPrefs.GetInt(GameConst.keyMaxStaminaNum, GameConst.defaultStaminaNum);
        nowStaminaNum = PlayerPrefs.GetInt(GameConst.keyNowStaminaNum, maxStaminaNum);
    }

    /// <summary>
    /// スタミナの最大値を保存する
    /// </summary>
    public void SaveMaxStaminaNum()
    {
        PlayerPrefs.SetInt(GameConst.keyMaxStaminaNum, maxStaminaNum);
        PlayerPrefs.Save();
    }

    /// <summary>
    /// スタミナの現在値を保存する
    /// </summary>
    public void SaveNowStaminaNum()
    {
        PlayerPrefs.SetInt(GameConst.keyNowStaminaNum, nowStaminaNum);
        PlayerPrefs.Save();
    }
}

 

ゲーム内時間を管理するクラス

ゲーム内時間関連も分けました。アプリを中断するときに時間を保存する機能が欲しくなったらここに追加しようと思います。

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

using Const;

/// <summary>
/// ゲーム内で使用する時間を扱うクラス
/// </summary>
public class GameTime
{
    private static GameTime _Instance = new GameTime();
    public static GameTime Instance()
    {
        return _Instance;
    }

    // ゲーム内で使用する時間
    public DateTime serverTime; // サーバから取得した時間
    public float startTime; // サーバ時間を取得完了した際に、アプリ起動からの経過時間を保存しておく
    public DateTime nowTime { get { return serverTime.AddSeconds(Time.realtimeSinceStartup - startTime); } } // 現在のアプリ時間
    public DateTime recoveryStartTime; // スタミナの回復を開始した時間
    public DateTime suspendTime; // 中断・終了時に保存した時間

    // 初期化完了時に呼びたいメソッドを登録する
    private Action<bool> delegateMethod;

    /// <summary>
    /// サーバ時間の初期化
    /// </summary>
    /// <param name="completed">完了時に呼び出すコールバックメソッド</param>
    public void InitServerTime(Action<bool> completed)
    {
        delegateMethod = completed;
        CoroutineHandler.StartStaticCoroutine(GetServerTime());
    }

    /// <summary>
    /// サーバ時間取得
    /// </summary>
    IEnumerator GetServerTime()
    {
        UnityWebRequest request = UnityWebRequest.Get(GameConst.urlServerTimeAPI);

        yield return request.SendWebRequest();

        if (request.isNetworkError || request.isHttpError)
        {
            Debug.Log(request.error);
            delegateMethod(false);
        }
        else
        {
            if (request.responseCode == 200)
            {
                SetServerTime(request.downloadHandler.text);
                delegateMethod(true);
            }
            else
            {
                Debug.Log("response code: " + request.responseCode);
                delegateMethod(false);
            }
        }
    }

    /// <summary>
    /// 取得したサーバ時間をstringで受け取り、DateTimeに変換してセットする
    /// </summary>
    /// <param name="serverTimeString">サーバ時間の文字列</param>
    public void SetServerTime(string serverTimeString)
    {
        serverTime = DateTime.Parse(serverTimeString);
        Debug.Log("取得したサーバ時間: " + serverTime);
        startTime = Time.realtimeSinceStartup;
        Debug.Log("取得が完了した際の、アプリ起動からの経過時間: " + startTime);
    }

    /// <summary>
    /// スタミナの回復を開始した時間を読み込む
    /// </summary>
    public void LoadRecoveryStartTime()
    {
        string s = PlayerPrefs.GetString(GameConst.keyRecoveryStartTime);

        // 読み込めない場合はデフォルト値で作成する
        try
        {
            recoveryStartTime = DateTime.FromBinary(Convert.ToInt64(s));
            Debug.Log("スタミナ回復を開始した時間を読み込みました: " + recoveryStartTime);
        }
        catch
        {
            recoveryStartTime = new DateTime(0);
            Debug.Log("スタミナ回復を開始した時間を新規作成しました: " + recoveryStartTime);
        }
    }

    /// <summary>
    /// スタミナの回復を開始した時間を保存する
    /// </summary>
    public void SaveRecoveryStartTime()
    {
        PlayerPrefs.SetString(GameConst.keyRecoveryStartTime, recoveryStartTime.ToBinary().ToString());
        PlayerPrefs.Save();
    }
}

 

スタミナを扱うクラス

今回作り直したものはこちら。

using UnityEngine;
using System;
using Const;

/// <summary>
/// 最近のスマホゲームによくある「スタミナ」を扱うクラス
/// </summary>
public class StaminaController : MonoBehaviour
{
    private bool isInitializeFinished = false; // 初期化完了フラグ
    private double restTime; // 休憩時間(1回復するまでの残り時間)

    void Update()
    {
        if (isInitializeFinished)
        {
            UpdateStamina();
        }
    }

    /// <summary>
    /// 初期化
    /// </summary>
    public void Init()
    {
        Debug.Log("前回スタミナ " + UserData.Instance().nowStaminaNum + " / " + UserData.Instance().maxStaminaNum
                  + " スタミナ回復を開始した時間:" + GameTime.Instance().recoveryStartTime);

        isInitializeFinished = true;
    }

    /// <summary>
    /// スタミナが最大値以上かどうか
    /// </summary>
    /// <returns><c>true</c>最大以上である <c>false</c>最大ではない</returns>
    public bool IsStaminaFull()
    {
        return UserData.Instance().nowStaminaNum >= UserData.Instance().maxStaminaNum;
    }

    /// <summary>
    /// スタミナの更新
    /// </summary>
    private void UpdateStamina()
    {
        // スタミナが最大以上の場合は何も行わない
        if (IsStaminaFull()) return;

        // 経過した時間を求める
        TimeSpan diff = GameTime.Instance().nowTime - GameTime.Instance().recoveryStartTime;
        double totalSeconds = diff.TotalSeconds;

        // 経過時間分のスタミナを回復させる
        int recoveryNum = 0;
        while (totalSeconds > GameConst.recoverTimePerStamina)
        {
            totalSeconds -= GameConst.recoverTimePerStamina;
            recoveryNum++;
            GameTime.Instance().recoveryStartTime = GameTime.Instance().recoveryStartTime.Add(TimeSpan.FromSeconds(GameConst.recoverTimePerStamina));
        }

        if(recoveryNum > 0)
        {
            RecoverStamina(recoveryNum);
            GameTime.Instance().SaveRecoveryStartTime();
        }

        restTime = GameConst.recoverTimePerStamina - totalSeconds + 1; // 「+1」->00:00を見せないため
    }

    /// <summary>
    /// スタミナを消費する
    /// </summary>
    /// <returns><c>true</c>スタミナを消費した <c>false</c>スタミナが不足している</returns>
    /// <param name="spendStaminaNum">消費するスタミナ値</param>
    public bool SpendStamina(int spendStaminaNum)
    {
        if (UserData.Instance().nowStaminaNum < spendStaminaNum) return false;

        // スタミナ消費後の値がスタミナ最大値を下回った際に時間を保存する(回復開始となる時間)
        if (UserData.Instance().nowStaminaNum >= UserData.Instance().maxStaminaNum
            && (UserData.Instance().nowStaminaNum - spendStaminaNum) < UserData.Instance().maxStaminaNum)
        {
            GameTime.Instance().recoveryStartTime = GameTime.Instance().nowTime;
            GameTime.Instance().SaveRecoveryStartTime();
        }

        UserData.Instance().nowStaminaNum -= spendStaminaNum;
        UserData.Instance().SaveNowStaminaNum();

        return true;
    }

    /// <summary>
    /// スタミナの最大値を増やす
    /// </summary>
    /// <param name="addNum">増やす値</param>
    public void AddStaminaMax(int addNum)
    {
        // 現在のスタミナが最大だった場合は回復を開始する
        if(IsStaminaFull())
        {
            GameTime.Instance().recoveryStartTime = GameTime.Instance().nowTime;
            GameTime.Instance().SaveRecoveryStartTime();
        }

        UserData.Instance().maxStaminaNum += addNum;
        UserData.Instance().SaveMaxStaminaNum();
    }

    /// <summary>
    /// スタミナを回復する
    /// </summary>
    /// <param name="num">回復する値</param>
    public void RecoverStamina(int num)
    {
        if (IsStaminaFull()) return;

        UserData.Instance().nowStaminaNum += num;
        if (UserData.Instance().nowStaminaNum > UserData.Instance().maxStaminaNum)
        {
            UserData.Instance().nowStaminaNum = UserData.Instance().maxStaminaNum;
        }
        UserData.Instance().SaveNowStaminaNum();
    }

    /// <summary>
    /// スタミナを最大値を無視して回復する
    /// </summary>
    /// <param name="num">回復する値</param>
    public void RecoverStaminaOverLimit(int num)
    {
        UserData.Instance().nowStaminaNum += num;
        UserData.Instance().SaveNowStaminaNum();
    }

    /// <summary>
    /// 最大値までスタミナを回復させる
    /// </summary>
    public void RecoverStaminaMax()
    {
        if (IsStaminaFull()) return;

        UserData.Instance().nowStaminaNum = UserData.Instance().maxStaminaNum;
        UserData.Instance().SaveNowStaminaNum();
    }

    /// <summary>
    /// 最大値分のスタミナを回復させる
    /// </summary>
    public void RecoverStaminaMaxOverLimit()
    {
        UserData.Instance().nowStaminaNum += UserData.Instance().maxStaminaNum;
        UserData.Instance().SaveNowStaminaNum();
    }

    /// <summary>
    /// スタミナが1回復するまでの時間を取得する
    /// </summary>
    /// <returns>残り時間</returns>
    public double GetRestTime()
    {
        return restTime;
    }
}

前回は初期化時にスタミナやらを渡していましたが、それぞれの機能を分けたので変更しています。

 

GameManager から初期化を呼ぶ

using UnityEngine;

public class GameManager : MonoBehaviour
{
    // StaminaControllerをInspectorから設定する
    [SerializeField]
    private StaminaController staminaController;

    void Start()
    {
        // スタミナとスタミナ回復を開始した時間をロードする
        UserData.Instance().LoadStamina();
        GameTime.Instance().LoadRecoveryStartTime();

        // サーバ時間を取得する
        // 完了時に呼んでもらいたいコールバックメソッドを渡している
        GameTime.Instance().InitServerTime(FinishedSetUpServerTime);
    }

    /// <summary>
    /// サーバ時間の設定が完了した際に呼ばれるコールバックメソッド
    /// </summary>
    /// <param name="result"><c>true</c>設定成功</param>
    public void FinishedSetUpServerTime(bool result)
    {
        if (result)
        {
            staminaController.Init();
        }
        else
        {
            // サーバ時間が取得できなかった場合の処理(リトライとか)
        }
    }
}

 

まずGameManagerからサーバ時間を取得するように呼び出しています。サーバ時間の取得が完了してからスタミナ回復やらを始めたいので、完了時に初期化が終わったことを教えてもらうためにコールバックメソッドを渡しています。

GameTimeクラスはMonoBehaviourを継承していないのでコルーチンを使うために「CoroutineHandler」を使っています。

「CoroutineHandler」については以下を参照。

MonoBehaviourを継承していないクラスでStartCoroutineを使う

 

また、スタミナの管理についてはUserDataクラスへ、現在時間やスタミナ回復を開始した時間等のゲーム内で使用する時間はGameTimeクラスに分けました。

 

これでサーバ時間を使用したスタミナ機能が作ることができました。が、スタミナの値をローカルで保持しているのでまだ完全とは言えない……。スタミナもサーバ側で管理するようにしたいのでまた勉強してきます!

コメント

タイトルとURLをコピーしました