ログインボーナス、スタミナの回復、放っておくと育つ畑・・・。スマホのゲームには「現実の時間」で進む仕組みがたくさんあります。でも、UnityのTime.timeやTime.deltaTimeは、ゲームを閉じると止まってしまいます。今回は、現実の日付と時刻を扱うDateTimeと、時間の長さを扱うTimeSpanをご紹介します!
この記事で学べること
Time.timeとDateTimeの違い(ゲームの中の時間と、現実の時刻)- 今の日付と時刻を取る方法(
DateTime.NowとDateTime.UtcNow) - 日付や時刻を文字にして表示する方法
- 2つの時刻の差(TimeSpan)を計算する方法
- 時刻を保存して、次に開いたときに「何時間たったか」を計算する方法
- 時刻を保存するタイミング(
OnApplicationPauseとOnApplicationQuit) - 日付が変わったかどうかの判定(ログインボーナス)
前提知識
- 変数でデータを保存しよう
- 時間を扱う処理:コルーチン・Invoke・一時停止:ゲームの中の時間(Time.time)
- データを保存しよう:PlayerPrefsとJSON:時刻の保存にPlayerPrefsを使います
2つの「時間」
Unityで「時間」を扱うものは、大きく分けて2つあります。
| Time.time / Time.deltaTime | DateTime | |
|---|---|---|
| 何の時間? | ゲームが始まってからの時間 | 現実の日付と時刻(パソコンやスマホの時計) |
| ゲームを閉じると? | 止まる(次に開くと0から) | 閉じている間も進む |
| 一時停止(timeScale:0)すると? | 止まる | 止まらない |
| 使いどころ | 移動、アニメーション、タイマー | ログインボーナス、スタミナの回復、放置ゲーム |
「ゲームを閉じている間に何時間たったか」は、Time.timeでは分かりません。そこでDateTimeを使います。
DateTime:今の日付と時刻
今の時刻を取る
まずは、今の時刻をConsoleに出してみましょう。
using System;
using UnityEngine;
// 今の時刻を Console に出す
public class NowSample : MonoBehaviour
{
private void Start()
{
DateTime now = DateTime.Now;
Debug.Log($"今の時刻:{now}");
Debug.Log($"年:{now.Year} 月:{now.Month} 日:{now.Day} 時:{now.Hour} 分:{now.Minute}");
Debug.Log($"世界標準時(UTC):{DateTime.UtcNow}");
}
}
- using System;
- DateTimeはUnityではなくC#の機能なので、
using System;が必要
- DateTimeはUnityではなくC#の機能なので、
- DateTime.Now
- パソコンやスマホに設定されている地域の、今の時刻(日本なら日本時間)
- DateTime.UtcNow
- 世界標準時(UTC)の、今の時刻。日本時間より9時間遅れている
- Year・Month・Day・Hour・Minute・Second
- 年・月・日・時・分・秒を、それぞれ数字で取り出せる
NowとUtcNow、どっちを使う?
- 保存や計算にはUtcNow
- Nowは地域の設定で変わる。スマホを持って海外に行ったり、地域の設定を変えたりすると、時刻がずれてしまう
- 夏時間がある国では、1年に1回、時計が1時間戻る日もある
- UTCは世界中で同じなので、「何時間たったか」の計算がずれない
- プレイヤーに見せるときはNow
- 表示は、その人の地域の時刻の方が分かりやすい
- UTCで保存した時刻は、
ToLocalTime()で地域の時刻に直せる
スライムとりあえず「保存と計算はUtcNow、表示はNow(またはToLocalTime)」と覚えておけばOKです
文字にして表示する
ToStringに書式を渡すと、好きな形の文字に出来ます。
using System;
using TMPro;
using UnityEngine;
// 今の日付と時刻を、テキストに表示する時計
public class ClockText : MonoBehaviour
{
[SerializeField] private TMP_Text clockText;
private void Update()
{
clockText.text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
}
| 書式 | 意味 | 例 |
|---|---|---|
| yyyy | 年(4けた) | 2026 |
| MM | 月(2けた) | 09 |
| dd | 日(2けた) | 22 |
| HH | 時(24時間、2けた) | 18 |
| mm | 分(2けた) | 30 |
| ss | 秒(2けた) | 15 |
MM(月)とmm(分)は、大文字と小文字で意味が違うので注意- 「9月22日」のように日本語を混ぜたいときは、
"M月d日"のように書ける(1けたなら1文字で書く)
TimeSpan:時間の長さ
2つの時刻の差を計算する
DateTimeどうしを引き算すると、TimeSpan(時間の長さ)になります。
DateTime start = DateTime.UtcNow;
// ……何かをする……
TimeSpan passed = DateTime.UtcNow - start;
Debug.Log($"{passed.TotalSeconds}秒たった");
TimeSpanからは、次のように長さを取り出せます。
| 書き方 | 意味 | 3時間12分30秒のとき |
|---|---|---|
| TotalHours | 全部を「時間」にした値(小数あり) | 3.208… |
| TotalMinutes | 全部を「分」にした値 | 192.5 |
| TotalSeconds | 全部を「秒」にした値 | 11550 |
| Hours | 「時間」の部分だけ | 3 |
| Minutes | 「分」の部分だけ | 12 |
| Seconds | 「秒」の部分だけ | 30 |
- 計算にはTotal〜を使う
- Hoursは「時間の部分だけ」なので、2日と3時間なら3になる(日の部分は入らない)
- 「何時間たったか」の計算には、TotalHoursを使う
- 表示にはHours・Minutesが便利
- 「3時間12分」と表示したいときは、
(int)passed.TotalHoursとpassed.Minutesを組み合わせる
- 「3時間12分」と表示したいときは、
TimeSpanを作る
「8時間」「30分」のような長さは、TimeSpanを作って表せます。
TimeSpan eightHours = TimeSpan.FromHours(8);
TimeSpan thirtyMinutes = TimeSpan.FromMinutes(30);
DateTime nextBonus = DateTime.UtcNow + eightHours; // 8時間後の時刻
時刻を保存して、たった時間を計算する
前回遊んだ時刻を覚えておく
ゲームを閉じたときに時刻を保存して、次に開いたときに「何時間ぶりか」を計算してみましょう。
using System;
using TMPro;
using UnityEngine;
// 前回遊んだ時刻を保存して、次に開いたときに「何時間ぶりか」を表示する
public class LastPlayed : MonoBehaviour
{
private const string LastPlayedKey = "LastPlayed";
[SerializeField] private TMP_Text messageText;
private void Start()
{
if (!PlayerPrefs.HasKey(LastPlayedKey))
{
messageText.text = "はじめまして!";
return;
}
// 保存しておいた文字を数字(long)に戻し、それを DateTime に戻す
long savedTimeBinary = long.Parse(PlayerPrefs.GetString(LastPlayedKey));
DateTime lastPlayed = DateTime.FromBinary(savedTimeBinary);
TimeSpan away = DateTime.UtcNow - lastPlayed;
string lastText = lastPlayed.ToLocalTime().ToString("yyyy/MM/dd HH:mm");
messageText.text = $"前回:{lastText}\n{(int)away.TotalHours}時間{away.Minutes}分ぶりです";
}
// スマホでホームに戻ったとき(アプリがバックグラウンドに入ったとき)
private void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
SaveNow();
}
}
// ゲームを終了したとき(エディタでは再生を止めたとき)
private void OnApplicationQuit()
{
SaveNow();
}
private void SaveNow()
{
PlayerPrefs.SetString(LastPlayedKey, DateTime.UtcNow.ToBinary().ToString());
PlayerPrefs.Save();
}
}
- ToBinary と FromBinary
- DateTimeを、1つの大きな整数(long)にしたり、元に戻したりする
- PlayerPrefsにはlongを保存するメソッドがないので、文字(string)にして保存している
- PlayerPrefs.HasKey
- 初めて遊ぶときは、まだ保存されていない。先に確認してから読み込む
- UTCで保存して、ToLocalTimeで表示
- 計算はUTCどうしで行い、プレイヤーに見せるときだけ地域の時刻に直す



floatで保存したくなりますが、floatは細かい数字を覚えきれず、時刻がずれてしまいます・・・時刻は文字かlongで保存しましょう
いつ保存する?
保存するタイミングは、PCとスマホで違います。
| メソッド | 呼ばれるとき | 注意 |
|---|---|---|
| OnApplicationQuit | ゲームを終了したとき。エディタでは再生を止めたとき | スマホでは、ホームに戻ったあとにアプリを消されると呼ばれないことがある |
| OnApplicationPause(true) | スマホでホームに戻ったとき、電話がかかってきたとき | PCではほとんど呼ばれない |
| OnApplicationFocus(false) | ほかのウインドウやアプリに切り替えたとき | 頻繁に呼ばれる |
- スマホで遊ぶゲームは、OnApplicationPauseとOnApplicationQuitの両方で保存しておくと安心
- スマホでは、ホームに戻ったアプリをOSが止めることがある。そのときOnApplicationQuitは呼ばれないので、OnApplicationPauseで保存しておくのが大事



「閉じるときに保存」だけだと、スマホでは保存されないことがあるんだね。PauseとQuitの両方で保存、覚えておいてね!
日付が変わったかを調べる(ログインボーナス)
「1日1回もらえるボーナス」は、前回もらった日と今日の日付を比べます。時刻まで比べると「24時間たったか」になってしまうので、Dateで日付だけを取り出します。
using System;
using UnityEngine;
// 日付が変わっていたら、1日1回のボーナスをあげる
public class DailyBonus : MonoBehaviour
{
private const string LastBonusKey = "LastBonus";
private void Start()
{
DateTime today = DateTime.Now.Date; // 今日の0時0分(地域の時刻)
if (PlayerPrefs.HasKey(LastBonusKey))
{
DateTime lastDay = DateTime.FromBinary(long.Parse(PlayerPrefs.GetString(LastBonusKey)));
if (lastDay >= today)
{
Debug.Log("今日のボーナスは、もうもらいました");
return;
}
}
Debug.Log("ログインボーナス!");
PlayerPrefs.SetString(LastBonusKey, today.ToBinary().ToString());
PlayerPrefs.Save();
}
}
- DateTime.Now.Date
- 時刻を0時0分にした、日付だけのDateTime
- 「日付が変わる」はプレイヤーの地域の0時なので、ここではNowを使う
- lastDay >= today
- DateTimeどうしは、
>や<で比べられる(あとの方が大きい)
- DateTimeどうしは、
端末の時計を変えられたら?
DateTimeはパソコンやスマホの時計なので、プレイヤーが時計を進めたり戻したりすると、その分だけ時間がたったことになってしまいます。
- マイナスになったら0にする
- 時計を戻されると、たった時間がマイナスになる。そのまま使うと、ゲージが増えたりしておかしくなる
- 上限を決める
- 「何時間放っておいても、進むのは最大24時間まで」のようにしておくと、時計を大きく進められても被害が小さい
TimeSpan away = DateTime.UtcNow - lastTime; // lastTime:保存しておいた前回の時刻(DateTime)
if (away < TimeSpan.Zero)
{
away = TimeSpan.Zero; // 時計を戻されたら、時間はたっていないことにする
}
if (away > TimeSpan.FromHours(24))
{
away = TimeSpan.FromHours(24); // 最大24時間まで
}
完全にずるを防ぐには、インターネットのサーバーから時刻をもらう必要があります。一人で遊ぶゲームなら、ここまでの対策で十分なことが多いです。
よくあるエラーと対処法
エラー:The name ‘DateTime’ does not exist in the current context
- 原因:
using System;を書いていない - 対処法:スクリプトの一番上に
using System;を足す
エラー:FormatException: Input string was not in a correct format.
- 原因:
long.Parseに、数字ではない文字(空の文字など)を渡している。保存する前に読み込んだ、キーの名前が違う、など - 対処法:
PlayerPrefs.HasKeyで確認してから読み込む。保存と読み込みで、キーの名前をそろえる(定数にしておくと安心)
エラー:たった時間がおかしい(9時間ずれる)
- 原因:保存はNow、計算はUtcNow(またはその逆)のように、NowとUtcNowが混ざっている
- 対処法:保存も計算もUtcNowにそろえる
エラー:2日たったのに「3時間」と表示される
- 原因:
TotalHoursではなくHoursを使っている(Hoursは「時間」の部分だけ) - 対処法:たった時間の計算にはTotalHours・TotalMinutes・TotalSecondsを使う
まとめ
Time.timeはゲームの中の時間(閉じると止まる)、DateTimeは現実の時刻(閉じている間も進む)- 保存と計算はDateTime.UtcNow、表示はDateTime.Now(またはToLocalTime)
- DateTimeどうしの引き算でTimeSpan。計算にはTotalHoursなどのTotal〜を使う
- 保存は
ToBinary()を文字にして、読み込みはFromBinary() - スマホではOnApplicationPauseとOnApplicationQuitの両方で保存する
- 時計を戻された(マイナス)ときと、進められたときの上限を決めておく
次のステップ
- DateTimeを使って、閉じている間もペットのおなかが減る放置ゲームを作る回:ペット育成ゲームの作り方#5
- Unityスタートガイド 目次
コメント