はじめに
こんにちは!MaTTaと申します。プログラミングスクールRunteq50期生です。
この度、卒業制作として「3日目に魔王がいる」というWEBアプリを開発し、MVP*リリースいたしました!
往年のレトロ RPG の世界観をベースにしており、AIによるオリジナルアバター生成やバトルモード、ショップなど様々なゲームフィケーション要素を通じて日々の記録の習慣化を支援します。
開発はまだ続きますが、一旦ご紹介させてください 💡
*Minimum Viable Product: 最小限のプロダクト
アプリ開発初学者のため、解釈の誤りや、妥当でない設計を含む場合があります。
ご指摘・アドバイスなどありましたらコメントにお寄せいただけますと幸いです。
サービスURL
本記事投稿時点でMVP段階につき、不具合や、予告のないデータ・仕様の修正について予めご容赦ください
Githubリポジトリ
アプリ名の由来
習慣化のためには3日坊主に打ち克つことが大切だと考え、そこをボス(魔王)に見立てています。英名である MAO は Motivation(モチベーション), Achievement(達成), Overcome(克服)のダブルミーニングです。
テーマ選定
このアプリは、「習慣化の難しさ」と「日々の成果の見えにくさ」を解消したいという思いで開発しました。
習慣化の難しさ
- 最初のモチベーションは高くてもすぐに情熱が薄れてしまう
- 一方で一旦習慣化されたら長期間継続できる
日々の成果の見えにくさ
- 年齢を重ねるごとに時間の経過が早くなり、記録が残っていないと虚しくなってくる
デザインの選定
- 好きなレトロ RPG の世界観から着想し、自分を冒険者に重ねて楽しみたかった
主な機能
機能 |
---|
ユーザー登録機能(Google 認証) |
生成 AI DALL-E3 モデル によるキャラクター(アバター)作成機能 |
活動報告とパラメータ上昇 |
活動履歴確認 |
アイテム購入機能 |
モンスターとのデイリーバトル(簡易) |
3日連続活動報告時に魔王バトル(簡易) |
他ユーザーの情報確認、いいね機能 |
ゲストログイン |
レトロRPG風UI
活動記録
活動振り返り
アバター生成
- 必要な項目を選択することで世界観にマッチしたオリジナルアバターを作成できます
- 過去のアバターは全て保存しており、メインアバターの設定も自在です
- OpenAI API のDall-e3モデルを使用しています
- RailsのActive Storageを用いAmazon S3に画像を保存しています
バトル
- レトロRPGさながらのターン制バトルに挑むことができます
- バトルエフェクトにはmo.jsを用いています
魔王戦
技術スタック
技術 | 詳細 | 選定理由 |
---|---|---|
バックエンド | Ruby 3.2.2 Ruby on Rails 7.1.3(APIモード) |
テーブル管理をしやすい点と豊富なライブラリによって機能追加しやすいため |
フロントエンド | React 18.2.0 | アバター生成やバトルなど、滑らかUIを容易に実現できるため |
データベース | MySQL 8.0 | 軽量シンプルで直感的であるため |
CSS | Tailwind CSS 3.0 DaisyUI 4.10.2 |
レスポンシブル対応含むユーザーフレンドリーなUIを容易に実現できるため |
認証 | OmniAuth 2.1.2 JSON With Token |
認証はGoogleのみであり、Deviseは過剰だと考えたため。 |
インフラ | Heroku (バック・フロント共) | 簡単にデプロイとスケーリングができるため |
API | OpenAI API (GPT-4, Dall-e3) | 生成AIにより多様性に富むアウトプットをユーザーに提供するため |
ストレージ | Amazon S3(ActiveStorage) | 生成画像保存用。安全かつスケーラブルで、RailsのActive Storageとの相性もよいため |
CI/CD | GitHub Actions | デプロイの自動化によって作業を効率化するため |
開発環境 | Docker | 一貫性のある開発環境を簡単に構築できるため |
分析 | Python | バトルバランス検討のシミュレーションのため |
構成図
Githubではアプリ全体で単一のリポジトリとしてコード管理しつつ、frontendとbackendそれぞれをherokuにデプロイしています
ER図(開発開始時点)
工夫した箇所
世界観
アプリの目的はあくまでもゲームではなくリアル世界の習慣化継続ですので、レトロRPGの世界観でコーディネートすることが大変でもあり楽しかった点です。
例えば
- 3日坊主を克服したい → 3日記録することで魔王の幻術が解ける → バトルに突入
- リアル世界の活動を記録する → 運動したら筋力が、学習したら知力ステータスが上がる
- ユーザー一覧 → 酒場という名称にする
生成AIの活用
世界観にもつながりますが、アバター画像や魔王のコメントで生成AIを用いる際に、相応しい出力を確度高く得られるようにプロンプトを試行錯誤しました。
アバター生成時のプロンプト
//ジョブ,年代,性別,性格は別途選択
const basePrompt =
"A pixel art image resembling a 32-bit era video game, depicting a fantasy RPG character. The character is designed with a highly detailed and vibrant pixel art style typical of the 32-bit era, featuring a complex color palette and intricate details, surpassing the 16-bit graphics. The character is in a dynamic pose, equipped with gear appropriate to their job, reflecting their role and abilities in the game. This showcases the advanced graphical capabilities and the spirit of epic adventures in more modern classic video games.";
const prompt = `${basePrompt} Job: ${selectedJob}, Gender: ${selectedGender}, Age: ${selectedAge}, Personality: ${selectedSupplement}`;
魔王のセリフ
def call
response = self.class.post('/chat/completions', body: {
model: @model,
messages: [
{role:'system', content: "あなたは悪の魔王です。魔王に相応しい傲慢な口調と性格をしています。これからユーザーと激しいバトルになることでしょう。まずユーザーが日頃の活動について、いくつか述べるので、戦闘前に魔王らしくシニカルに讃えてください。ユーザーの活動内容の情報が少ない場合はそれらに触れず無難なセリフで構いません。また、全ての活動に触れる必要もなく、最大2つまででいいです"},
{ role: 'user', content: @message }
]
}.to_json, headers: @options[:headers])
raise response.parsed_response['error']['message'] unless response.success?
response.parsed_response['choices'][0]['message']['content']
end
直感的なUI
なかなか100%満足がいくまでには到達していないのですが、DaisyUIも活用しながらスマホでも操作できるような設計を目指しています。
データハンドリング
usersを中心として、アイテムやステータス、アバターなど様々なテーブルが関連付けらており、それを効率的にRailsからReactにJSON形式で受け渡す必要があります。Userモデルでデータを制御しフロントでストレスなくデータを扱うことができるようになりました。とはいえ、Userモデルが煩雑になっているのでリファクタリングの検討対象です。
def as_json(options = {})
if options[:index_view]
super(options.merge(
methods: [:latest_avatar_url, :latest_status_as_json, :latest_job],
include: {
users_items: { include: { item: { only: [:id, :name, :cost, :item_url, :category] } }, only: [:amount] },
coin: { only: [:amount] },
activities: {
include: {
category: { only: [:id, :name] }
},
only: [:id, :action, :minute]
}
}
))
else
super(options.merge(
methods: [:latest_status, :latest_job, :latest_avatar_url],
include: {
users_items: { include: { item: { only: [:id, :name, :cost, :item_url, :category] } }, only: [:amount] },
coin: { only: [:amount] },
avatars: { only: [:id, :avatar_url] },
activities: {
include: {
category: { only: [:id, :name] }
},
only: [:id, :action, :minute, :created_at]
},
battle_logs: { only: [:id, :enemy_id, :result, :created_at] },
boss_battle_logs: { only: [:id, :enemy_id, :result, :created_at] }
}
)).tap do |hash|
hash[:latest_status] = latest_status_as_json
end
end
end
バトルバランス
成長を実感でき、かつインフレしすぎないバトルバランスの設計は非常に難しいです。Pythonで仮想バトルを設定し、10000回の戦歴結果を分析を繰り返し、ある程度それっぽいバランスにまとめることができました。
あるステータス、あるモンスターでの10000回シミュレーションの例
今後実装したい機能
機能 | 備考 |
---|---|
モンスター追加と討伐図鑑 | やりこみ要素 |
コレクターアイテム追加とアイテム図鑑 | やりこみ要素 |
ランキング機能 | やりこみ要素 |
パーティ機能 | SNS要素 |
活動記録のグラフ化 | 習慣化支援 |
レイドバトル | SNS要素 |
UIの洗練 | UX |
アバターコンテスト | SNS |
通知機能 | 習慣化支援 |
開発ふりかえり
アプリ
「大切なのは自分が欲しいと思えるアプリかどうか」という哲学(のようなもの)を意識しながら開発してきましたが、その点では割と満足いっています。
技術的にはSNS認証、生成AI、S3、デプロイなど小難しい部分があったものの、それぞれミニアプリイベントで検証を済ませていたので思いの外サクサク進めることができました。とにかくアプリを作ってみることが大切なんだと思います。
とはいえ、Reactの状態管理や、Railsのテーブル設計はいまだに鈍臭い箇所が多く・・・・もっともっと精進したいところです。
スクール生活
働きながらでも一応動くアプリが作れるようになるんだなあという達成感はあります。
入学した頃は、特にRailsのカリキュラムをこなすことに必死でした。毎日新しい概念や技術に触れるたび呆然としていましたが、同期はもちろん、違う入学期の方々ともリアル・バーチャルで交流し、お互いに教え教わることで、理解が深まり、徐々に知識の幅も広がっていきました。コミュニティって本当に大事。
卒業して終わりではなく今後もコミュニティ参加や学習を習慣化し、技術力を高めていきたいと思います。
おわりに
今回はアプリの概要の紹介でしたが、今後シリーズ記事として各機能の実装方法をまとめていきたいと思います。
【この記事を書いた人のX】