はじめに
Todoアプリって、だいたい続かない。
UIを頑張っても
機能を盛っても
3日後には開かれなくなる。
そこで作ったのが、
タスクを完了すると感情が変化・成長するバーチャルペット「ぴよこ」 を組み込んだ Todo アプリ。
この記事では、
- なぜ「感情」を持たせたのか
- どうやって気分を計算しているのか
- 実装上のポイント
を、実際の TypeScript コードを交えて解説する。
なぜバーチャルペットなのか
結論から言うと、
「タスク完了=即時フィードバック」が弱いから続かない
チェックボックスが埋まるだけだと、脳が全然喜ばない。
そこで、
- タスク完了 → 反応する存在
- 行動量 → 感情に反映
- ランダム要素 → 予測不能性
この3点を満たす仕組みとして、
感情を持つペット という形にした。
実装した体験
- タスク完了でぴよこが喜ぶ(エフェクト+メッセージ)
- 時間帯・曜日・進捗・ランダム要素で気分が変化
- 16種類の感情 + 30%でサブ感情
- 3日連続達成で「ありがとう」状態
- 10%で理不尽に「おなかすいた」
「ちゃんと生きてる感」を最優先。
アーキテクチャ概要
Frontend: React + TypeScript + Zustand
↓ API
Backend: Express + TypeScript
↓ ORM
Database: SQLite (Prisma)
気分計算はすべてバックエンドで行い、
フロントは 結果を表示するだけ にしている。
感情の型定義
拡張前提なので、ベース感情と拡張感情を分離。
export type BaseMood = 'happy' | 'normal' | 'tired' | 'sad';
export type ExtendedMood =
| 'excited'
| 'relaxed'
| 'curious'
| 'sleepy'
| 'playful'
| 'proud'
| 'hungry'
| 'cozy'
| 'energetic'
| 'dreamy'
| 'grateful'
| 'lonely';
export type PiyocoMood = BaseMood | ExtendedMood;
export interface MoodCandidate {
mood: PiyocoMood;
weight: number;
reason: string;
}
weight(重み) を持たせているのが重要。
時間帯による気分補正
朝・昼・夕方・夜で傾向を変える。
function getTimeBasedMoodTendency(): MoodCandidate[] {
// morning / afternoon / evening / night
}
ここは完全に「雰囲気作り」用。
ユーザー行動には直接関係しない。
曜日補正
月曜は疲れ、金曜はテンション高め。
function getDayBasedMoodTendency(): MoodCandidate | null {
// 日付による軽い補正
}
影響度は低め。
あくまで味付け。
タスク進捗ベースの気分(最重要)
ここがコア。
- 今日の完了率
- 直近7日間の完了率
- 完了タスク数
をスコア化する。
let score = 50;
if (todayTasks > 0) score += (todayCompleted / todayTasks) * 25;
if (totalTasks > 0) score += (completedTasks / totalTasks) * 15;
if (todayCompleted > 0) score += Math.min(todayCompleted * 3, 10);
設計意図
- 何もしてなくても「普通」にはなる
- 今日の行動を最優先で評価
- タスク0件は「lonely」
ユーザーを責めない設計。
ランダム変動(生き物感)
if (Math.random() < 0.1) {
// 突然の気分変化
}
予測できるAIは飽きる。
理不尽さは正義。
最終的な気分決定
すべての候補を集めて、
重み付きランダム選択。
const totalWeight = candidates.reduce((s, c) => s + c.weight, 0);
let random = Math.random() * totalWeight;
さらに30%でサブ感情を付与。
UIへの反映
感情ごとに、
- 表示名
- メッセージ
- 行動テキスト
を分離定義。
const MOOD_MESSAGES: Record<PiyocoMood, string[]> = {
happy: ['今日はご機嫌だよ', ...],
};
ランダム文言必須。
同じ台詞はすぐ飽きる。
タスク完了コンボ
10秒以内に連続完了するとコンボ発生。
- エフェクト強化
- まとめて消化を促す
const COMBO_TIMEOUT_MS = 10000;
地味だけど、行動パターンが変わる。
結果
数値はまだ検証中だが、
- 連続起動率の向上
- まとめてタスク消化する傾向
- 「ぴよこ見に来た」という動機
が明確に増えた。
学び
- ゲーミフィケーションは 派手さより設計
- 感情は「報酬」として機能する
- ランダム性は少量が一番効く
Todoアプリを作るなら、
「完了した後に何が起きるか」 を設計した方がいい。
チェックマークだけじゃ弱い。
技術スタック
- React 18
- TypeScript
- Zustand
- Express
- Prisma
- SQLite