はじめに
RUNTEQでの卒業制作として、ゲーム時間管理アプリを開発しました!
Game Exit
GitHub
概要
「ゲーム時間が長引いてしまう」を解決する!
ゲーム仲間との共同プレイにも目を向けた「ゲーム時間管理アプリ」です。
サービス開発の背景
私自身、昔から大のゲーム好きでした。
しかし課題として、 「ゲーム時間の管理」 が難しいと私自身も感じていましたし、一緒にゲームをプレイしたことのある友人も同じような課題を抱えていました。
実際、 「あっという間に時間が過ぎている」 、 「いざやめようとしても、なかなかやめられない」 という課題を感じている人は多いはずです。
また、ゲーム時間が延び過ぎることで、「睡眠不足」や「人間関係への影響」といった弊害も生じることがあります。
そこで、ゲーム時間を適切に管理することで、ゲームライフをより充実させたいと考え、本アプリを開発しました。
クリアしたい課題
① ゲーム時間が延びる「根本原因」を、複数の視点から探せること
② 共同プレイ中に、ゲーム仲間に「やめよう」と切り出しやすくなること
主な機能
アラーム・記録機能を主軸に、ゲーム時間管理を複数の方向から手助けできるように設計しました。
アラーム機能
ゲームの開始時間と終了時間を設定し、終了時間になったらブラウザから通知が届きます。
なお、リマインダーの設定も可能です。
フレンドとのアラーム共有機能
フレンドをアラームに招待することでアラームを共有できます。
アラームに招待されたフレンドにもブラウザからの通知が届きます。
プレイ時間の記録機能
プレイ時間の記録を、グラフ等により複数の視点で確認できます。
定型文機能
ゲーム仲間にやめるのを切り出しづらい場合に、事前に用意した定型文を即座にコピーし使用できます。
診断機能
質問に答え、診断結果を確認することで、自分のゲームとの向き合い方を振り返ることができます。
PWAインストール
ブラウザからの通知が、ブラウザを閉じていても届くようにするために、PWA化を実装しました。
機能面での差別化ポイント
前提
クリアしたい課題の1つとして、 『共同プレイ中に、ゲーム仲間に「やめよう」と切り出しやすくなること』 があります。
この課題をクリアするための機能
- 「アラーム共有機能」 や 「定型文機能」 で、共同プレイ時にも決めた時間通りにやめやすくしました。
- プレイ時間の記録の統計画面に、 「一緒にプレイしたフレンドごとの引き延ばし時間」 のグラフが表示されるようにし、 誰とプレイしたときに時間が延びがちか を確認できるようにしました。
技術スタック
| カテゴリ | 技術 |
|---|---|
| バックエンド | Ruby on Rails 8 |
| フロントエンド | Hotwire / Tailwind CSS 4 |
| データベース | PostgreSQL |
| バックグラウンドジョブのキューアダプタ | Good Job |
| ブラウザからのプッシュ通知 | Web Push(gem)、Push API |
| アラームカレンダー | Simple Calendar |
| 画像処理関連 | Active Storage / libvips / Cloudinary |
| 検索 | Ransack |
| ページネーション | Pagy |
| 開発環境の構築 | Docker |
| 本番環境のWebサービス | Render |
| 本番環境のDBサービス | Supabase |
| 計測 | Google Analytics |
技術選定理由
バックグラウンドジョブのキューアダプタに Good Job を選んだ理由
・ SidekiqはRedis(インフラ)が追加で必要で導入コストが高い。
・ Solid Queueは管理UIがなく別途gemが必要。
⭐ Good Job はDBだけで動作し、ジョブの実行履歴・エラー・リトライ状況を確認できる管理画面が標準で組み込まれている。
→ 詳細記事( https://qiita.com/Shiro_yy/items/6bb5e147ddd7c15736ad )
アラームカレンダーに SimpleCalendar を選んだ理由
・ Google Calendar APIは外部カレンダーとの連携が不要な今回の要件には過剰と判断。
・ Full Calendarほどの高度なフロントエンド機能が今回の要件には不要。また、JavaScriptの関心事が増えてしまう。
⭐ Simple Calendar はERBテンプレートが標準で用意されており、Railsの文脈だけで完結するため、今回の要件にフィット。
→ 詳細記事( https://qiita.com/Shiro_yy/items/c1239e748f3844331cbb )
画像処理関連に Cloudinary / Active Storage / libvips を選んだ理由
⭐ Active Storage はRails標準として安定してメンテナンスされている。
⭐ Cloudinary は導入手順がシンプルで日本語の実装記事が豊富。
⭐ libvips はImage Magickと比べて高速かつメモリ効率が高い。
→ 詳細記事( https://qiita.com/Shiro_yy/items/141767ae5e5cc2dce033 )
本番環境のWebサービスに Render を選んだ理由
・ Fly.ioは無料プランが廃止済み
・ Koyeb(無料プラン)はリージョンがフランクフルトまたはワシントンDCに限られレイテンシが大きい。
⭐ Render はシンガポールリージョンがある。また、GitHub連携による自動デプロイ(CD)を無料枠で簡単に設定できる。
→ 詳細記事( https://qiita.com/Shiro_yy/items/f90fd41ccabcd727ecfb )
本番環境のDBサービスに Supabase を選んだ理由
・ Renderの無料プランは30日で失効する。
・ Neon無料プランは稼働時間に制限があるためバックグラウンドジョブのポーリングと相性が悪い。
・ Aiven無料プランは稼働時間の制限がなくシンプルに使えるが、シンガポールリージョンに対応していない。(RenderのWebサービスのリージョンがシンガポールであるため、WebサービスとDBサービスの間でレイテンシが発生してしまう。)
⭐ Supabase無料プラン はAivenが満たしている要件を満たしているのに加え、シンガポールリージョンがある。
→ 詳細記事( https://qiita.com/Shiro_yy/items/58f055639796f92d1d6f )
ER図
テーブル構成は大きく分けて、 「ユーザー情報」、「アラーム・記録」、「定型文」、「その他」 の4つに分類されます。
ユーザー情報に関するテーブル
-
ユーザー名やメールアドレス、アイコン画像などの基本的な情報を
usersテーブルで管理しています。 -
プッシュ通知の購読情報を
web_push_subscriptionsテーブルで管理しており、usersテーブルと関連付けることで、ユーザーごとに複数デバイスへの通知を送れる設計にしています。
アラーム・記録に関するテーブル
-
アラームの設定情報を管理する
alarmsテーブルとusersテーブルを関連付けることで、ユーザーごとにアラームを作成・管理できるようにしています。 -
アラームへの参加状況や通知の送信状態を管理する
alarm_membershipsテーブルを作成し、alarmsテーブル、usersテーブルと関連付けています。これにより、フレンドと共有したアラームに対して、参加者ごとの通知状態を個別に管理できます。 -
アラームがストップされた際の記録を
alarm_logsテーブルで管理しており、プレイ時間やストップまでの時間などの記録データをユーザごとに保存できる設計にしています。
定型文に関するテーブル
- 定型文を管理する
message_templatesテーブル、定型文のブックマーク情報を保存するbookmarksテーブルを作成し、それぞれusersテーブルと関連付けています。
その他のテーブル
-
ユーザー間のフレンド関係を管理する
friendshipsテーブルについては、statusカラムを用いて 「フレンド申請中」・「フレンド申請承認済み」 の状態を管理できるようにしました。 -
ゲーム時間管理度診断の結果を記録する
diagnosis_resultsテーブルをusersテーブルと関連付けることで、ユーザーごとに診断履歴を管理できる設計にしています(※診断履歴の確認機能は今後実装する予定)。
こだわった実装
ダークモードのちらつき防止
<問題点>
「アプリを開いたときのダークモードへの切り替え」をJSファイル側で実装すると、ページ読み込み時に
一瞬ライトモードで表示される ちらつきが発生 します。
ブラウザはHTMLを上から順に処理するため、JSファイルとして読み込むスクリプトの実行は画面の描画(レンダリング)処理より後になります。
<解決策>
インラインスクリプトをCSSより前に配置 することで、最初の画面の描画より前にダークモードのクラス(dark)を確定させ、ちらつきをゼロにしています。
app/views/layouts/application.html.erb
<%# CSSより前に配置 %>
<script>
const saved = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const isDark = saved === "dark" || (saved !== "light" && prefersDark);
if (isDark) document.documentElement.classList.add("dark");
</script>
<!-- 中略 -->
<%# CSS %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%# JS %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
ユーザー検索機能のプライバシー保護
<問題点>
ユーザー検索機能では、検索ワードがない状態で全ユーザーを表示してしまうと、 本名で登録しているユーザーの名前が意図せず一覧画面に表示されてしまうリスク があります。
<解決策>
パラメータに検索ワードがない場合はUser.noneを返すことで、 データベースへの問い合わせ自体を発行せず、一覧画面に何も表示しない設計 にしています。
app/controllers/users_controller.rb
base_scope = params.dig(:q, :name_cont).present? ? @q.result : User.none
ユーザーから頂いたFBとその対応
MVPリリース時にいただいた改善案
- 「定型文が、デフォルトデータだけでなく、作成・編集機能があれば便利」 → 対応済み
- 「スマホ対応があれば嬉しい」 → 対応済み
- 「アラームストップ時のイラストやメッセージがあれば達成感を得られそう」 → メッセージと絵文字、色分けを追加
- 『アラームを「解除」という表現が、分かりづらい。「削除」と重なってしまっている』 → 「ストップ」という表現に変えた
- 「定型文をワンクリックでコピーできれば便利」 → 対応済み
- 「ゲーマーが夜にゲームをすることを想定して、ダークモードを用意したらいいかもしれない」 → 対応済み
本リリース後に頂いた改善案
- 「プレイしたゲームごとにプレイ時間を管理できると良さそう」 → 対応予定
- 「初回ログイン時に年齢聞いて、それに応じてUIが変わったら良さそう。(大人用、子供用に分けるなど)」 → 検討中
- 「Discordにも通知が送れたら嬉しい」 → 検討中
- 「診断機能で診断したものが記録として保存されると、自分のゲームとの関わり方を過去と比較できて面白そう」 → 対応予定
- 「白黒がメインなので、強調したい部分は色を変えてもいいかもしれない」 → 対応予定
- 『定型文機能についてですが、「みんながどんな言い回し使ってるのかな」というのも気になるので、参考として見ることができたらなお嬉しい』 → 対応予定
その他(お褒めいただいた点)
- シンプルなカラーだからこそ、カラーテーマの切り替えがあるのがすごくいい
- カレンダーに登録or招待されたアラームが表示されるから見やすい。
- プレイ記録統計が見れるのは、データ見るの大好き人間からしたらめっちゃお気に入りポイントです!
- 使い方が直感的でUIUXも迷いずらく、わかりやすい
- メニューボタンにアイコンがあるので分かりやすい
- 定型文があるのは、ゲーマーならではの発想で面白い
おわりに
ユーザーの方々から頂いたFBを元に、適宜改善していきます!
Game Exit