🕐

[Unity]日付変更までの時間をカウントダウンする

2024/11/21に公開

はじめに

ソシャゲにありがちな日付変更システム周りに関して、日付変更までの残り時間を表示するといった記事が意外となかったので残しておきます。
デイリーミッションとか期間限定ショップなど、日替わり要素で使えるはず。


動画で言うと、Nowの隣に表示されているのが現在時刻。
その下、Remainの隣に表示されているのが、日をまたぐまでのカウントダウンです。

24:00:00 から 01:06:00 引いているので、およそ 22:53:59 になっています。

ソースコード

とりあえずソースコード

Timer.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UniRx;
using UniRx.Triggers;

public class Timer : MonoBehaviour
{
    //! 現在時刻テキスト
    [SerializeField]
    protected TextMeshProUGUI _nowTimeText = null;

    //! 残り時刻テキスト
    [SerializeField]
    protected TextMeshProUGUI _remainTimeText = null;

    private void Awake()
    {
        // 繰り返し処理
        this.UpdateAsObservable().Subscribe( _ => 
        {
            // 現在時刻の取得
            DateTimeOffset nowTime = DateTimeOffset.Now;

            // 現在時刻テキスト
            {
                // フォーマット
                string format = nowTime.ToString("HH:mm:ss");

                // テキストへ反映
                _nowTimeText.text = format;
            }

            // 残り時刻テキスト
            {
                // 日付変更時刻の取得
                DateTimeOffset dateChangeTime = DateTimeOffset.Now.Date.AddDays(1);

                // 残り時間の計算
                TimeSpan remainTime = dateChangeTime - nowTime;

                // フォーマット
                string format = remainTime.ToString(@"hh\:mm\:ss");

                // テキストへ反映
                _remainTimeText.text = format;
            }

        }).AddTo(this);
    }
}

簡単な解説

    // 繰り返し処理
    this.UpdateAsObservable().Subscribe( _ => 
    {
        // この中の処理をマイフレーム行う
    }).AddTo(this);

こちらはUniRxの機能で、{}内の処理を毎フレーム行います。
今回の記事とは趣旨がずれるので詳細は割愛しますが、Update()のようなものだと思ってください。

    // 現在時刻の取得
    DateTimeOffset nowTime = DateTimeOffset.Now;

ここで現在時刻を取得します。
この時の現在時刻とは、使用している端末で設定されている時刻です。
そのため、意図的に端末の時刻をいじれば、DateTimeOffset.Now で取得する値も変わります。

また、DateTimeOffset型はDateTime型と違い、世界協定時刻 (UTC) との時差も考慮していますので、端末の国を変更した場合にも変動します。

そもそもDateTime(DateTimeOffset)型とは、時間のある一点を表すことができる型です。
〇年〇月〇日の〇時〇分〇秒、といった具合です。
https://learn.microsoft.com/ja-jp/dotnet/api/system.datetimeoffset?view=net-8.0

    // フォーマット
    string format = nowTime.ToString("HH:mm:ss");

    // テキストへ反映
    _nowTimeText.text = format;

現在時刻をUIへと反映させます。
テキストで時刻を表示できるように、DateTimeOffset型をstring型へと変換します。
今回は、現在時刻を〇時〇分〇秒の形で表示します。


続いて、日付変更までのカウントダウンの処理です。

    // 日付変更時刻の取得
    DateTimeOffset dateChangeTime = DateTimeOffset.Now.Date.AddDays(1);

DateTimeOffset.Now.Date の部分は、端末で設定されている日付の00時00分を返します。

そこに対して、さらに AddDays(1) というメソッドを呼び出しています。
これは、指定された時刻に日数を足すことができます。
今回は、DateTimeOffset.Now.Date を指定し、そこに1日足しているので、次の日の00時00分になるわけです。
つまり、今日の24時00分です。

例) 現在が 11/20 15:00 の場合

  • DateTimeOffset.Now => 11/20 15:00
  • DateTimeOffset.Now.Date => 11/20 00:00
  • DateTimeOffset.Now.Date.AddDays(1) => 11/21 00:00
    // 残り時間の計算
    TimeSpan remainTime = dateChangeTime - nowTime;

ここで日付変更までの残り時間を計算します。
前述のとおり、dateChangeTimeには今日の24時00分が格納されているので、そこから現在時刻を引いてあげれば、24時までの残り時間が割り出せます。

ここで注意するべきなのが、型が変わっている点です。
DateTimeOffset型同士の引き算にも関わらず、計算結果はTimeSpan型で返ってきています。

TimeSpan型とは読んで字のごとく、ある一定期間を表すことができます。
〇日間、〇時間、〇分間、といった感じです。
前述ですが、DateTimeOffset型はある時刻の一点を表すものです。
「24時から15時を引いた結果は?」と聞かれたら、「9時」と答えますよね。
なので、DateTimeOffset型同士の引き算の結果は、TimeSpan型になるのです。
https://learn.microsoft.com/ja-jp/dotnet/api/system.timespan?view=net-8.0

    // フォーマット
    string format = remainTime.ToString(@"hh\:mm\:ss");

    // テキストへ反映
    _remainTimeText.text = format;

残り時刻をUIへと反映させるためにstring型へと変換します。
ここではTimeSpan型からstring型への変換です。

DateTimeOffset型から変換する時との違いは、ToString()に渡している中身です。
DateTimeOffset型とTimeSpan型では、フォーマット方法が微妙に違うので注意してください。

補足

1.

    // フォーマット
    string format = nowTime.ToString("HH:mm:ss");

フォーマットの際にToString()に渡している文字列によって、表示する時刻のデータを設定できます。
yy/MM/ddをつけてあげれば年月日、zzzで時差などが表示可能です。
※日付を表すのが MM 、分数を表すのが mm

例) 例) 現在が 日本の2024/11/20 15:00 の場合

  • yy/MM/dd => 2024/11/20
  • zzz => +09:00 (日本の時差)
  • yy/MM/dd HH:mm:ss zzz => 2024/11/20 15:00 +09:00

https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/custom-date-and-time-format-strings

2.

    // 日付変更時刻の取得
    DateTimeOffset dateChangeTime = DateTimeOffset.Now.Date.AddDays(1);

AddDays() の他にも、AddHours()やAddMinutes()も存在します。
これらを使えば、朝5時までの残り時間などを表現することも可能です。
ソシャゲとかにありがちですね。

3.

記事の途中でも触れましたが、ここで記述した内容は基本的に端末の設定時刻に依存します。
言ってしまえば、ユーザーが時を操ることができます。

例えば、DateTimeOffsetを使って日付変更を検知し、でデイリーボーナスを実装したとします。
すると、ユーザーが端末の時刻を進めたり戻したりを繰り返すだけでいくらでも日付を跨げるので、デイリーボーナスを受け取り放題になってしまいます。

オフラインのゲームでは、このようなチート行為(?)の対策は完璧に行うのはなかなかに厳しいです。
そのため、使用する場面には十分な注意が必要です。

終わりに

何か誤っている記述などあれば、指摘いただければと思います。
少しでもお役に立てれば幸いです。

Discussion