🚧 デモシステム
🐶 PetLife AI
あなたの生活習慣がペットに影響する、リアル連動型AIペット
🐕
元気いっぱい!
📊 今日のあなた
👟 歩数
8,524歩
😴 睡眠時間
7時間30分
🍎 食事
健康的 ✓
📚 実装ガイド
リアル生活連動型AIペットアプリの構築手順
HealthKit連携 × AI学習 × リアルタイム反応システム
1
開発環境とアーキテクチャ設計
リアル生活データを取得する技術選定
- 開発言語: Swift(iOS)、Kotlin(Android)
- ヘルスデータ: HealthKit(iOS)、Google Fit(Android)
- AI エンジン: Core ML、TensorFlow Lite
- バックエンド: Firebase Realtime Database
- 位置情報: Core Location、Google Maps API
- 通知: Firebase Cloud Messaging
🛠️ 推奨技術スタック
Swift(iOS)
HealthKit
Core ML
Firebase
Core Location
AVFoundation
📦 iOS開発環境構築
# Xcodeでプロジェクト作成 # File > New > Project > App # Podfileに必要なライブラリを追加 platform :ios, '15.0' use_frameworks! target 'PetLifeAI' do pod 'Firebase/Database' pod 'Firebase/Auth' pod 'Firebase/Storage' pod 'Lottie' end # ライブラリインストール pod install
2
HealthKit連携(歩数・睡眠・運動データ)
飼い主の生活習慣データをリアルタイム取得
- 歩数データ: 毎日の歩数を取得、目標歩数設定
- 睡眠データ: 就寝時刻、起床時刻、睡眠時間
- 運動データ: ランニング、サイクリングなど
- 心拍数: Apple Watchから取得(オプション)
- バックグラウンド取得: アプリを閉じても自動更新
- プライバシー配慮: ユーザーの許可必須
🏃 HealthKitでデータ取得(Swift)
import HealthKit
class HealthDataManager {
let healthStore = HKHealthStore()
func requestAuthorization(completion: @escaping (Bool) -> Void) {
// 取得したいデータタイプを定義
let readTypes: Set = [
HKObjectType.quantityType(
forIdentifier: .stepCount
)!,
HKObjectType.quantityType(
forIdentifier: .distanceWalkingRunning
)!,
HKObjectType.categoryType(
forIdentifier: .sleepAnalysis
)!,
HKObjectType.quantityType(
forIdentifier: .heartRate
)!
]
// HealthKitの使用許可をリクエスト
healthStore.requestAuthorization(
toShare: nil,
read: readTypes
) { success, error in
completion(success)
}
}
func getTodaySteps(completion: @escaping (Int) -> Void) {
guard let stepType = HKQuantityType.quantityType(
forIdentifier: .stepCount
) else { return }
// 今日の開始時刻
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(
withStart: startOfDay,
end: now,
options: .strictStartDate
)
let query = HKStatisticsQuery(
quantityType: stepType,
quantitySamplePredicate: predicate,
options: .cumulativeSum
) { _, result, error in
guard let result = result,
let sum = result.sumQuantity() else {
completion(0)
return
}
let steps = Int(sum.doubleValue(
for: HKUnit.count()
))
completion(steps)
}
healthStore.execute(query)
}
func getSleepData(completion: @escaping (TimeInterval) -> Void) {
guard let sleepType = HKObjectType.categoryType(
forIdentifier: .sleepAnalysis
) else { return }
// 過去24時間
let now = Date()
let yesterday = Calendar.current.date(
byAdding: .day,
value: -1,
to: now
)!
let predicate = HKQuery.predicateForSamples(
withStart: yesterday,
end: now,
options: .strictStartDate
)
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierEndDate,
ascending: false
)
let query = HKSampleQuery(
sampleType: sleepType,
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: [sortDescriptor]
) { _, samples, error in
guard let samples = samples as? [HKCategorySample]
else {
completion(0)
return
}
var totalSleepTime: TimeInterval = 0
for sample in samples {
// asleepのみをカウント
if sample.value == HKCategoryValueSleepAnalysis
.asleep.rawValue {
totalSleepTime += sample.endDate
.timeIntervalSince(sample.startDate)
}
}
completion(totalSleepTime)
}
healthStore.execute(query)
}
func startObservingHealthData() {
// 歩数の変更を監視
guard let stepType = HKQuantityType.quantityType(
forIdentifier: .stepCount
) else { return }
let query = HKObserverQuery(
sampleType: stepType,
predicate: nil
) { query, completionHandler, error in
// 歩数が更新されたらペットに影響
self.getTodaySteps { steps in
PetManager.shared.updateFromSteps(steps)
}
completionHandler()
}
healthStore.execute(query)
// バックグラウンドでも監視を有効化
healthStore.enableBackgroundDelivery(
for: stepType,
frequency: .hourly
) { success, error in
print("Background delivery: \(success)")
}
}
}
💡 HealthKit許可のコツ: なぜこのデータが必要なのか、ペットの成長にどう影響するかを明確に説明すると、許可率が大幅に向上します。
3
ペットのステータス管理とAI反応システム
リアルデータに基づいてペットが動的に変化
- ステータス管理: 体力、幸福度、愛情度、空腹度
- 時間経過システム: リアルタイムで数値が変動
- 影響計算: 歩数が多い→体力UP、睡眠不足→体力DOWN
- 感情表現: ステータスに応じて表情・動きが変化
- AI学習: 飼い主の行動パターンを学習
- 通知システム: 「お腹空いてるよ!」など
🐕 ペット管理システム
import Foundation
class Pet: Codable {
var name: String
var type: PetType // dog, cat
var age: Int // 日数
var health: Int = 100 // 0-100
var happiness: Int = 100
var love: Int = 50
var hunger: Int = 50 // 0=満腹, 100=空腹
var lastFed: Date = Date()
var lastPlayed: Date = Date()
var experience: Int = 0
var level: Int = 1
enum PetType: String, Codable {
case dog, cat, rabbit
}
}
class PetManager {
static let shared = PetManager()
var currentPet: Pet?
private var updateTimer: Timer?
func startPet() {
// 定期的にステータスを更新
updateTimer = Timer.scheduledTimer(
withTimeInterval: 60, // 1分ごと
repeats: true
) { _ in
self.updatePetStatus()
}
}
func updatePetStatus() {
guard let pet = currentPet else { return }
// 時間経過でステータス変化
let now = Date()
let hoursSinceFed = now.timeIntervalSince(
pet.lastFed
) / 3600
let hoursSincePlayed = now.timeIntervalSince(
pet.lastPlayed
) / 3600
// 空腹度が増加(3時間で最大)
pet.hunger = min(100, Int(hoursSinceFed * 33))
// 幸福度が減少(遊んでないと)
if hoursSincePlayed > 6 {
pet.happiness = max(0, pet.happiness - 5)
}
// 空腹すぎると体力低下
if pet.hunger > 80 {
pet.health = max(0, pet.health - 2)
}
// HealthKitデータから影響を受ける
updateFromHealthData()
// ステータスに応じて通知
sendNotificationIfNeeded(pet: pet)
// データを保存
savePet()
}
func updateFromHealthData() {
guard let pet = currentPet else { return }
// 歩数の影響
HealthDataManager().getTodaySteps { steps in
if steps >= 10000 {
// 目標達成で体力・幸福度UP
pet.health = min(100, pet.health + 5)
pet.happiness = min(100, pet.happiness + 5)
} else if steps < 3000 {
// 運動不足で体力DOWN
pet.health = max(0, pet.health - 3)
}
}
// 睡眠の影響
HealthDataManager().getSleepData { sleepTime in
let hours = sleepTime / 3600
if hours >= 7 {
// 十分な睡眠で体力回復
pet.health = min(100, pet.health + 10)
} else if hours < 5 {
// 睡眠不足で体力・幸福度DOWN
pet.health = max(0, pet.health - 5)
pet.happiness = max(0, pet.happiness - 5)
}
}
}
func feedPet() {
guard let pet = currentPet else { return }
pet.hunger = max(0, pet.hunger - 40)
pet.health = min(100, pet.health + 5)
pet.happiness = min(100, pet.happiness + 5)
pet.lastFed = Date()
pet.experience += 10
checkLevelUp()
savePet()
// アニメーション表示
showPetReaction(message: "美味しい! 😋")
}
func playWithPet() {
guard let pet = currentPet else { return }
pet.happiness = min(100, pet.happiness + 15)
pet.love = min(100, pet.love + 10)
pet.hunger = min(100, pet.hunger + 10)
pet.lastPlayed = Date()
pet.experience += 20
checkLevelUp()
savePet()
showPetReaction(message: "楽しい!⚽")
}
func petPet() {
guard let pet = currentPet else { return }
pet.love = min(100, pet.love + 10)
pet.happiness = min(100, pet.happiness + 5)
pet.experience += 5
savePet()
showPetReaction(message: "気持ちいい〜 ❤️")
}
func checkLevelUp() {
guard let pet = currentPet else { return }
let requiredExp = pet.level * 100
if pet.experience >= requiredExp {
pet.level += 1
pet.experience -= requiredExp
showPetReaction(
message: "レベルアップ!Lv.\(pet.level) 🎉"
)
}
}
func sendNotificationIfNeeded(pet: Pet) {
if pet.hunger > 80 {
sendNotification(
title: "\(pet.name)が呼んでるよ!",
body: "お腹が空いてるみたい... 🍖"
)
}
if pet.happiness < 30 {
sendNotification(
title: "\(pet.name)が寂しそう",
body: "遊んであげて!⚽"
)
}
if pet.health < 50 {
sendNotification(
title: "\(pet.name)の体調が心配...",
body: "運動と睡眠を意識してね 😴"
)
}
}
func getMoodText() -> String {
guard let pet = currentPet else {
return "元気いっぱい!"
}
if pet.health < 30 {
return "ぐったり..."
} else if pet.happiness > 80 {
return "超ハッピー!✨"
} else if pet.hunger > 70 {
return "お腹空いた〜"
} else if pet.love > 80 {
return "大好き!❤️"
} else {
return "元気だよ!"
}
}
}
⚠️ バランス調整: ステータスの変化が激しすぎるとユーザーがストレスを感じます。ゲームデザイナーと相談して、適切なバランスを見つけましょう。
4
位置情報連動とリアル世界との融合
外出先でペットが反応する仕組み
- 公園検知: 公園に行くとペットが喜ぶ
- ペットショップ検知: おやつを買ってあげられる
- 動物病院検知: 健康診断イベント発生
- 天気連動: 雨の日は室内遊び、晴れは散歩
- 時間帯連動: 朝は散歩、夜は寝る時間
- 季節イベント: 春は花見、夏は海、冬は雪遊び
📍 位置情報連動システム
import CoreLocation
class LocationManager: NSObject,
CLLocationManagerDelegate {
static let shared = LocationManager()
private let locationManager = CLLocationManager()
private var currentLocation: CLLocation?
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy =
kCLLocationAccuracyBest
}
func requestAuthorization() {
locationManager.requestWhenInUseAuthorization()
}
func startMonitoring() {
locationManager.startUpdatingLocation()
// 天気情報を取得
fetchWeatherData()
}
func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.last else {
return
}
currentLocation = location
// 近くのスポットをチェック
checkNearbyPlaces(location: location)
}
func checkNearbyPlaces(location: CLLocation) {
// 公園を検索
searchNearbyPlaces(
location: location,
type: "park"
) { places in
if let park = places.first {
let distance = location.distance(
from: park.location
)
if distance < 100 { // 100m以内
PetManager.shared.triggerEvent(
.arrivedAtPark
)
}
}
}
// ペットショップを検索
searchNearbyPlaces(
location: location,
type: "pet_store"
) { places in
if let store = places.first {
let distance = location.distance(
from: store.location
)
if distance < 50 {
PetManager.shared.triggerEvent(
.nearPetStore
)
}
}
}
}
func fetchWeatherData() {
guard let location = currentLocation else {
return
}
let url = "https://api.openweathermap.org/data/2.5/weather"
let params = [
"lat": location.coordinate.latitude,
"lon": location.coordinate.longitude,
"appid": "YOUR_API_KEY"
]
// API呼び出し(簡略化)
// 実際の実装ではURLSessionを使用
// 天気に応じてペットの反応を変える
let weather = "sunny" // rainy, cloudy, etc
switch weather {
case "sunny":
PetManager.shared.showPetReaction(
message: "いい天気!散歩に行こう!☀️"
)
case "rainy":
PetManager.shared.showPetReaction(
message: "雨だね...お家で遊ぼう 🌧️"
)
default:
break
}
}
}
// ペットイベントシステム
extension PetManager {
enum PetEvent {
case arrivedAtPark
case nearPetStore
case morningWalk
case bedtime
}
func triggerEvent(_ event: PetEvent) {
guard let pet = currentPet else { return }
switch event {
case .arrivedAtPark:
pet.happiness = min(100, pet.happiness + 20)
pet.experience += 30
showPetReaction(
message: "公園だ!走りたい!🏃"
)
case .nearPetStore:
showPetReaction(
message: "おやつ買ってくれる?🍖"
)
case .morningWalk:
pet.health = min(100, pet.health + 10)
showPetReaction(
message: "朝の散歩、気持ちいいね!"
)
case .bedtime:
if Calendar.current.component(
.hour,
from: Date()
) > 22 {
showPetReaction(
message: "もう寝る時間だよ...😴"
)
}
}
savePet()
}
}
💡 AR機能追加: ARKitを使って、実際の公園にペットを表示する機能を追加すると、さらに没入感が高まります。ペットと一緒に散歩している感覚を味わえます。
5
AI会話システムと性格学習
飼い主との会話でペットが賢くなる
- 音声認識: 飼い主の声を認識して反応
- 自然言語理解: OpenAI GPTで会話を理解
- 性格学習: 話しかけ方でペットの性格が変化
- 感情分析: 声のトーンから感情を読み取る
- 記憶システム: 過去の会話を覚えている
- 音声合成: ペットが話す(可愛い声で)
🗣️ AI会話システムの実装
import Speech
import AVFoundation
class PetAIChat {
static let shared = PetAIChat()
private let speechRecognizer = SFSpeechRecognizer(
locale: Locale(identifier: "ja-JP")
)
private var recognitionRequest:
SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask:
SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
private var conversationHistory: [Message] = []
private var petPersonality: PetPersonality =
.friendly
enum PetPersonality {
case friendly, shy, energetic, calm
}
struct Message {
let role: String // user or assistant
let content: String
let timestamp: Date
}
func startListening(
completion: @escaping (String) -> Void
) {
// マイクの使用許可
SFSpeechRecognizer.requestAuthorization {
status in
guard status == .authorized else { return }
}
recognitionRequest =
SFSpeechAudioBufferRecognitionRequest()
let inputNode = audioEngine.inputNode
guard let recognitionRequest =
recognitionRequest else { return }
recognitionRequest.shouldReportPartialResults =
true
recognitionTask = speechRecognizer?
.recognitionTask(
with: recognitionRequest
) { result, error in
if let result = result {
let text = result
.bestTranscription
.formattedString
if result.isFinal {
completion(text)
self.stopListening()
}
}
}
let recordingFormat = inputNode.outputFormat(
forBus: 0
)
inputNode.installTap(
onBus: 0,
bufferSize: 1024,
format: recordingFormat
) { buffer, _ in
recognitionRequest.append(buffer)
}
audioEngine.prepare()
try? audioEngine.start()
}
func stopListening() {
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0)
recognitionRequest?.endAudio()
recognitionTask?.cancel()
}
func chatWithPet(
userMessage: String,
completion: @escaping (String) -> Void
) {
// 会話履歴に追加
conversationHistory.append(Message(
role: "user",
content: userMessage,
timestamp: Date()
))
// ペットの状態を取得
guard let pet = PetManager.shared.currentPet
else { return }
// OpenAI APIで返答を生成
let systemPrompt = """
あなたは\(pet.name)という名前の\(pet.type)です。
性格: \(getPersonalityDescription())
現在の状態:
- 体力: \(pet.health)
- 幸福度: \(pet.happiness)
- 愛情度: \(pet.love)
- 空腹度: \(pet.hunger)
飼い主に対して、ペットらしく可愛く返答してください。
絵文字を使って感情豊かに。
短めの返答(30文字以内)で。
"""
callOpenAI(
systemPrompt: systemPrompt,
userMessage: userMessage
) { response in
// 会話履歴に追加
self.conversationHistory.append(Message(
role: "assistant",
content: response,
timestamp: Date()
))
// 会話内容から性格を学習
self.learnFromConversation(
userMessage: userMessage,
petResponse: response
)
// 音声で読み上げ
self.speakText(response)
completion(response)
}
}
func callOpenAI(
systemPrompt: String,
userMessage: String,
completion: @escaping (String) -> Void
) {
let url = URL(string:
"https://api.openai.com/v1/chat/completions"
)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue(
"Bearer YOUR_API_KEY",
forHTTPHeaderField: "Authorization"
)
request.addValue(
"application/json",
forHTTPHeaderField: "Content-Type"
)
let body: [String: Any] = [
"model": "gpt-4",
"messages": [
["role": "system",
"content": systemPrompt],
["role": "user",
"content": userMessage]
],
"max_tokens": 100,
"temperature": 0.8
]
request.httpBody = try? JSONSerialization
.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) {
data, response, error in
guard let data = data,
let json = try? JSONSerialization
.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as?
[[String: Any]],
let message = choices.first?["message"]
as? [String: Any],
let content = message["content"] as?
String else {
completion("ワン!🐕")
return
}
completion(content)
}.resume()
}
func learnFromConversation(
userMessage: String,
petResponse: String
) {
// 飼い主の話し方を分析
let lowerMessage = userMessage.lowercased()
// 優しく話しかけられる → 性格がfriendlyに
if lowerMessage.contains("可愛い") ||
lowerMessage.contains("いい子") ||
lowerMessage.contains("好き") {
adjustPersonality(towards: .friendly)
}
// 元気に話しかけられる → energeticに
if lowerMessage.contains("遊ぼう") ||
lowerMessage.contains("行こう") ||
lowerMessage.contains("走ろう") {
adjustPersonality(towards: .energetic)
}
// 静かに話しかけられる → calmに
if lowerMessage.contains("おやすみ") ||
lowerMessage.contains("静かに") ||
lowerMessage.contains("落ち着いて") {
adjustPersonality(towards: .calm)
}
}
func adjustPersonality(
towards target: PetPersonality
) {
// 性格を徐々に変化させる
petPersonality = target
// ペットの反応速度や表情も変わる
PetManager.shared.updatePersonality(
petPersonality
)
}
func speakText(_ text: String) {
let synthesizer = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(
string: text
)
// 日本語で読み上げ
utterance.voice = AVSpeechSynthesisVoice(
language: "ja-JP"
)
// 声の高さを調整(可愛く)
utterance.pitchMultiplier = 1.3
utterance.rate = 0.5
synthesizer.speak(utterance)
}
func getPersonalityDescription() -> String {
switch petPersonality {
case .friendly:
return "人懐っこくて優しい性格"
case .shy:
return "恥ずかしがり屋で控えめ"
case .energetic:
return "元気いっぱいで活発"
case .calm:
return "落ち着いていて穏やか"
}
}
}
⚠️ API費用: OpenAI APIは1回あたり2〜10円。毎日10回会話すると月600〜3,000円。無料枠を使い切ったユーザーには課金プランを提示しましょう。
6
ペットの成長システムと見た目変化
時間経過と育て方で見た目が変わる
- 成長段階: 子犬 → 成犬 → 老犬(リアル日数で)
- 毛色変化: 健康的に育てると綺麗な毛色に
- 体型変化: 運動不足で太る、運動で引き締まる
- アクセサリー: 首輪、服、帽子などカスタマイズ
- レアカラー: 特別な育て方で珍しい色に
- 進化システム: 条件達成で特別な見た目に
🎨 成長と見た目変化システム
import UIKit
class PetGrowthManager {
static let shared = PetGrowthManager()
enum GrowthStage {
case baby // 0-7日
case child // 8-30日
case adult // 31-180日
case senior // 181日以降
}
enum BodyType {
case slim, normal, chubby
}
func getCurrentStage(age: Int) -> GrowthStage {
switch age {
case 0...7:
return .baby
case 8...30:
return .child
case 31...180:
return .adult
default:
return .senior
}
}
func updatePetAppearance(pet: Pet) {
let stage = getCurrentStage(age: pet.age)
let bodyType = calculateBodyType(pet: pet)
let furColor = calculateFurColor(pet: pet)
// ペットのスプライト画像を更新
let spriteName = getPetSprite(
type: pet.type,
stage: stage,
bodyType: bodyType,
color: furColor
)
// UIを更新
NotificationCenter.default.post(
name: .petAppearanceChanged,
object: nil,
userInfo: ["sprite": spriteName]
)
}
func calculateBodyType(pet: Pet) -> BodyType {
// 運動量(歩数データ)から体型を計算
HealthDataManager().getTodaySteps { steps in
let averageSteps = self.getAverageSteps(
days: 7
)
if averageSteps > 10000 {
return .slim
} else if averageSteps < 3000 {
return .chubby
} else {
return .normal
}
}
return .normal
}
func calculateFurColor(pet: Pet) -> UIColor {
// 健康状態から毛色を計算
let healthScore = (
pet.health +
pet.happiness +
pet.love
) / 3
if healthScore > 80 {
// 健康的 → 明るい色
return UIColor(
red: 0.9,
green: 0.7,
blue: 0.4,
alpha: 1.0
)
} else if healthScore < 40 {
// 不健康 → くすんだ色
return UIColor(
red: 0.5,
green: 0.4,
blue: 0.3,
alpha: 1.0
)
} else {
// 普通
return UIColor(
red: 0.7,
green: 0.5,
blue: 0.3,
alpha: 1.0
)
}
}
func checkForEvolution(pet: Pet) -> Bool {
// 特別な進化条件をチェック
// 完璧育成(全ステータス90以上)
if pet.health >= 90 &&
pet.happiness >= 90 &&
pet.love >= 90 &&
pet.level >= 10 {
evolve(pet: pet, to: .golden)
return true
}
// 運動マスター(毎日1万歩×30日)
let consecutiveDays = getConsecutive10kStepsDays()
if consecutiveDays >= 30 {
evolve(pet: pet, to: .athletic)
return true
}
return false
}
enum SpecialForm {
case golden // 完璧育成
case athletic // 運動マスター
case wise // 会話マスター
case royal // レア育成
}
func evolve(pet: Pet, to form: SpecialForm) {
// 特別な見た目に変化
showEvolutionAnimation()
// ステータスボーナス
pet.health = min(100, pet.health + 20)
pet.happiness = min(100, pet.happiness + 20)
// バッジを獲得
unlockAchievement(form: form)
PetManager.shared.savePet()
}
func showEvolutionAnimation() {
// Lottieアニメーションで進化演出
NotificationCenter.default.post(
name: .showEvolutionAnimation,
object: nil
)
}
}
// アクセサリーシステム
class PetAccessoryManager {
static let shared = PetAccessoryManager()
var ownedAccessories: [Accessory] = []
var equippedAccessories: [AccessoryType: Accessory]
= [:]
enum AccessoryType {
case collar, hat, clothes, toy
}
struct Accessory {
let id: String
let name: String
let type: AccessoryType
let rarity: Rarity
let imageUrl: String
let price: Int // ゲーム内通貨
enum Rarity {
case common, rare, epic, legendary
}
}
func buyAccessory(
_ accessory: Accessory,
completion: @escaping (Bool) -> Void
) {
// コインで購入
let coins = UserDefaults.standard
.integer(forKey: "coins")
if coins >= accessory.price {
UserDefaults.standard.set(
coins - accessory.price,
forKey: "coins"
)
ownedAccessories.append(accessory)
completion(true)
} else {
completion(false)
}
}
func equipAccessory(_ accessory: Accessory) {
equippedAccessories[accessory.type] =
accessory
// ペットの見た目を更新
PetGrowthManager.shared.updatePetAppearance(
pet: PetManager.shared.currentPet!
)
}
}
💡 ガチャ要素: レアなアクセサリーをガチャで入手できる仕組みを追加すると、収益化に繋がります。ただし、過度な課金誘導は避けましょう。
7
ソーシャル機能(他のペットとの交流)
友達のペットと遊べる機能
- ドッグラン機能: 他のユーザーのペットと遊ぶ
- 散歩マッチング: 近くのペットと散歩
- 写真共有: ペットの写真をSNSシェア
- ランキング: レベル、歩数、幸福度
- 対戦機能: ペット同士でミニゲーム
- プレゼント: おやつをプレゼントできる
👥 ソーシャル機能の実装
import Firebase
class SocialManager {
static let shared = SocialManager()
let db = Firestore.firestore()
func getNearbyPets(
location: CLLocation,
radius: Double = 1000, // 1km
completion: @escaping ([PetProfile]) -> Void
) {
// GeoFirestoreで近くのペットを検索
let center = GeoPoint(
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude
)
db.collection("pets")
.whereField("isOnline", isEqualTo: true)
.getDocuments { snapshot, error in
guard let documents = snapshot?.documents
else {
completion([])
return
}
var nearbyPets: [PetProfile] = []
for doc in documents {
let data = doc.data()
guard let geoPoint = data["location"]
as? GeoPoint else { continue }
let petLocation = CLLocation(
latitude: geoPoint.latitude,
longitude: geoPoint.longitude
)
let distance = location.distance(
from: petLocation
)
if distance <= radius {
let profile = PetProfile(
data: data
)
nearbyPets.append(profile)
}
}
completion(nearbyPets)
}
}
func enterDogPark(
completion: @escaping ([PetProfile]) -> Void
) {
// 現在オンラインの全ペットを取得
db.collection("dogpark")
.whereField("timestamp",
isGreaterThan: Date()
.addingTimeInterval(-300))
.getDocuments { snapshot, error in
let pets = snapshot?.documents
.compactMap {
PetProfile(data: $0.data())
} ?? []
completion(pets)
}
// 自分のペットをドッグパークに登録
guard let pet = PetManager.shared.currentPet,
let userId = Auth.auth().currentUser?.uid
else { return }
db.collection("dogpark").document(userId)
.setData([
"pet_name": pet.name,
"pet_type": pet.type.rawValue,
"level": pet.level,
"owner_id": userId,
"timestamp": FieldValue.serverTimestamp()
])
}
func playWithPet(
_ otherPet: PetProfile,
completion: @escaping (PlayResult) -> Void
) {
guard let myPet = PetManager.shared.currentPet
else { return }
// 相性チェック
let compatibility = calculateCompatibility(
myPet: myPet,
otherPet: otherPet
)
// 遊んだ結果
let happinessGain = Int(compatibility * 20)
let expGain = 50
myPet.happiness = min(
100,
myPet.happiness + happinessGain
)
myPet.experience += expGain
PetManager.shared.savePet()
let result = PlayResult(
success: true,
happinessGain: happinessGain,
expGain: expGain,
message: "楽しく遊んだよ!"
)
completion(result)
// 相手にも通知
sendPlayNotification(to: otherPet.ownerId)
}
func calculateCompatibility(
myPet: Pet,
otherPet: PetProfile
) -> Double {
var score = 0.5
// 同じ種類だと相性UP
if myPet.type.rawValue == otherPet.type {
score += 0.2
}
// レベルが近いと相性UP
let levelDiff = abs(myPet.level - otherPet.level)
if levelDiff < 5 {
score += 0.2
}
// 性格が合うと相性UP
// 実装...
return min(1.0, score)
}
func sharePhoto(image: UIImage) {
// SNSシェア機能
let activityVC = UIActivityViewController(
activityItems: [
image,
"私のペットを見て!#PetLifeAI"
],
applicationActivities: nil
)
// 共有完了でコインをプレゼント
let coins = UserDefaults.standard
.integer(forKey: "coins")
UserDefaults.standard.set(
coins + 50,
forKey: "coins"
)
}
}
struct PetProfile {
let ownerId: String
let name: String
let type: String
let level: Int
let location: GeoPoint?
init(data: [String: Any]) {
self.ownerId = data["owner_id"] as? String
?? ""
self.name = data["pet_name"] as? String ?? ""
self.type = data["pet_type"] as? String ?? ""
self.level = data["level"] as? Int ?? 1
self.location = data["location"] as? GeoPoint
}
}
struct PlayResult {
let success: Bool
let happinessGain: Int
let expGain: Int
let message: String
}
⚠️ プライバシー保護: 位置情報の共有は任意にし、ユーザーが安心して使えるよう配慮しましょう。子供が使う場合は保護者の同意を得る仕組みも必要です。
8
収益化とApp Storeデプロイ
持続可能なビジネスモデルの構築
- 基本無料: 1匹目のペットは無料で飼える
- プレミアム会員: 月額480円で複数飼育、特典
- アイテム課金: レアなアクセサリー、おやつ
- 広告収益: 動画広告でコイン獲得
- サブスクリプション: Apple App Store決済
- 審査対策: ガイドライン遵守、説明充実
💳 App内課金の実装
import StoreKit
class IAPManager: NSObject,
SKProductsRequestDelegate,
SKPaymentTransactionObserver {
static let shared = IAPManager()
private var products: [SKProduct] = []
private var purchaseCompletion:
((Bool) -> Void)?
// 商品ID
enum ProductID: String {
case premiumMonthly =
"com.petlifeai.premium.monthly"
case coins100 =
"com.petlifeai.coins.100"
case coins500 =
"com.petlifeai.coins.500"
case specialAccessory =
"com.petlifeai.accessory.golden"
}
override init() {
super.init()
SKPaymentQueue.default().add(self)
}
func fetchProducts() {
let productIDs: Set = [
ProductID.premiumMonthly.rawValue,
ProductID.coins100.rawValue,
ProductID.coins500.rawValue,
ProductID.specialAccessory.rawValue
]
let request = SKProductsRequest(
productIdentifiers: productIDs
)
request.delegate = self
request.start()
}
func productsRequest(
_ request: SKProductsRequest,
didReceive response: SKProductsResponse
) {
products = response.products
// 商品情報をUIに表示
NotificationCenter.default.post(
name: .productsLoaded,
object: products
)
}
func purchase(
productID: ProductID,
completion: @escaping (Bool) -> Void
) {
guard let product = products.first(where: {
$0.productIdentifier ==
productID.rawValue
}) else {
completion(false)
return
}
purchaseCompletion = completion
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
func paymentQueue(
_ queue: SKPaymentQueue,
updatedTransactions transactions:
[SKPaymentTransaction]
) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
handlePurchased(transaction)
case .failed:
handleFailed(transaction)
case .restored:
handleRestored(transaction)
case .deferred, .purchasing:
break
@unknown default:
break
}
}
}
func handlePurchased(
_ transaction: SKPaymentTransaction
) {
let productID = transaction.payment
.productIdentifier
// 購入処理
switch productID {
case ProductID.premiumMonthly.rawValue:
activatePremium()
case ProductID.coins100.rawValue:
addCoins(100)
case ProductID.coins500.rawValue:
addCoins(500)
case ProductID.specialAccessory.rawValue:
unlockAccessory("golden_collar")
default:
break
}
SKPaymentQueue.default()
.finishTransaction(transaction)
purchaseCompletion?(true)
}
func handleFailed(
_ transaction: SKPaymentTransaction
) {
SKPaymentQueue.default()
.finishTransaction(transaction)
purchaseCompletion?(false)
}
func activatePremium() {
UserDefaults.standard.set(
true,
forKey: "isPremium"
)
// プレミアム特典を有効化
// - 複数ペット飼育可能
// - 広告非表示
// - 特別なアクセサリー
showAlert(
title: "プレミアム会員登録完了!",
message: "特典をお楽しみください 🎉"
)
}
func addCoins(_ amount: Int) {
let currentCoins = UserDefaults.standard
.integer(forKey: "coins")
UserDefaults.standard.set(
currentCoins + amount,
forKey: "coins"
)
NotificationCenter.default.post(
name: .coinsUpdated,
object: nil
)
}
}
// 広告システム
import GoogleMobileAds
class AdManager: NSObject, GADFullScreenContentDelegate {
static let shared = AdManager()
private var rewardedAd: GADRewardedAd?
func loadRewardedAd() {
let request = GADRequest()
GADRewardedAd.load(
withAdUnitID: "ca-app-pub-xxx",
request: request
) { ad, error in
if let error = error {
print("広告読み込みエラー: \(error)")
return
}
self.rewardedAd = ad
self.rewardedAd?.fullScreenContentDelegate =
self
}
}
func showRewardedAd(
from viewController: UIViewController,
completion: @escaping (Bool) -> Void
) {
guard let ad = rewardedAd else {
completion(false)
return
}
ad.present(fromRootViewController: viewController) {
let reward = ad.adReward
print("報酬: \(reward.amount)")
// コインを付与
IAPManager.shared.addCoins(50)
completion(true)
}
}
}
📋 App Store審査対策
# Info.plist設定 <key>NSHealthShareUsageDescription</key> <string> あなたの歩数や睡眠データを読み取り、 ペットの健康状態に反映します。 データは端末内でのみ使用され、 外部に送信されることはありません。 </string> <key>NSLocationWhenInUseUsageDescription</key> <string> 現在地を取得し、近くの公園やペットショップで 特別なイベントを発生させます。 位置情報は他のユーザーと共有されません。 </string> <key>NSMicrophoneUsageDescription</key> <string> ペットと音声で会話するために マイクを使用します。 </string> # App Store審査ノート 【アプリの概要】 PetLife AIは、ユーザーの実生活と連動する AIペット育成アプリです。 【主な機能】 1. HealthKitと連携し、歩数・睡眠データを取得 2. 生活習慣がペットの健康状態に影響 3. AIによる会話機能 4. 位置情報で特別なイベント発生 5. 他のユーザーとの交流 【HealthKit使用目的】 - 歩数: ペットの運動量に反映 - 睡眠: ペットの体力回復に影響 データは端末内でのみ使用し、 サーバーに送信しません。 【課金要素】 - プレミアム会員: 月額480円 - コイン購入: 120円〜 - レアアクセサリー: 250円〜 すべて任意課金で、基本機能は無料です。 【テストアカウント】 Email: test@petlifeai.com Password: TestPass123! すでにペットを1匹飼育している状態で テスト可能です。
🚀 デプロイチェックリスト
✅ プライバシーポリシー
✅ 利用規約
✅ スクリーンショット
✅ アプリプレビュー動画
✅ サポートURL
✅ レビューノート
💡 審査通過のコツ: HealthKitの使用目的を明確に説明し、「ゲームのギミック」ではなく「ユーザーの健康促進」を前面に出すと審査が通りやすくなります。
💰 開発・運用コスト見積もり
初期開発費用(MVP)
150〜200万円
サーバー費用(Firebase)
月5,000〜15,000円
OpenAI API
月10,000〜50,000円
Apple Developer Program
年12,980円
イラスト・アニメーション
30〜80万円
音声・BGM制作
10〜30万円
月額運用費(目安)
2〜10万円
本格版開発費用
300〜500万円
💡 収益シミュレーション:
月額480円 × 5,000人(課金率15%) = 月間売上240万円
月額480円 × 20,000人(課金率20%) = 月間売上1,920万円
アイテム課金売上(月50万円)を含めると、初期投資を6〜12ヶ月で回収可能
月額480円 × 5,000人(課金率15%) = 月間売上240万円
月額480円 × 20,000人(課金率20%) = 月間売上1,920万円
アイテム課金売上(月50万円)を含めると、初期投資を6〜12ヶ月で回収可能
✅ 成功させるための重要ポイント
1. リアル連動の「驚き」を作る
• 「今日たくさん歩いたからペットが元気!」という実感
• 寝不足の日はペットも元気ない、というリアルさ
• 公園に行ったらペットが喜ぶ、という偶然の発見
• この「リアルとの繋がり」が最大の差別化
2. 「本当に生きてる感」の演出
• AI会話で飼い主を覚えて成長する
• 時間帯で行動が変わる(朝は散歩、夜は寝る)
• 無視すると拗ねる、構うと喜ぶ
• 通知で「寂しいよ」「お腹空いた」と呼びかける
3. 健康促進アプリとして訴求
• 「ペットのために運動しよう」と動機づけ
• 実際に歩数が増える→ユーザーの健康改善
• メディアに「健康アプリ」として取り上げてもらう
• App Storeの「ヘルスケア」カテゴリで上位狙う
4. 感情移入させる仕掛け
• 名前をつけさせる、誕生日を設定
• 成長過程を写真で保存、思い出アルバム
• 「○○日一緒に過ごしたよ」と節目を祝う
• 老犬になったら切ない演出(泣かせる)
5. SNS拡散を狙う
• ペットの写真をシェアしやすく
• 「今日のペットの一言」を自動生成
• レアな見た目のペットは自慢したくなる
• ハッシュタグキャンペーンで口コミ拡散
• 「今日たくさん歩いたからペットが元気!」という実感
• 寝不足の日はペットも元気ない、というリアルさ
• 公園に行ったらペットが喜ぶ、という偶然の発見
• この「リアルとの繋がり」が最大の差別化
2. 「本当に生きてる感」の演出
• AI会話で飼い主を覚えて成長する
• 時間帯で行動が変わる(朝は散歩、夜は寝る)
• 無視すると拗ねる、構うと喜ぶ
• 通知で「寂しいよ」「お腹空いた」と呼びかける
3. 健康促進アプリとして訴求
• 「ペットのために運動しよう」と動機づけ
• 実際に歩数が増える→ユーザーの健康改善
• メディアに「健康アプリ」として取り上げてもらう
• App Storeの「ヘルスケア」カテゴリで上位狙う
4. 感情移入させる仕掛け
• 名前をつけさせる、誕生日を設定
• 成長過程を写真で保存、思い出アルバム
• 「○○日一緒に過ごしたよ」と節目を祝う
• 老犬になったら切ない演出(泣かせる)
5. SNS拡散を狙う
• ペットの写真をシェアしやすく
• 「今日のペットの一言」を自動生成
• レアな見た目のペットは自慢したくなる
• ハッシュタグキャンペーンで口コミ拡散
❌ よくある失敗パターン
失敗1: HealthKit連携が面倒で離脱
→ 解決策: 許可なしでも遊べるモードを用意。後から許可を促す
失敗2: ステータス変化が激しすぎる
→ 解決策: 数時間放置してもペットは死なない。ゆるい設計に
失敗3: 課金圧が強すぎる
→ 解決策: 無課金でも十分楽しめる。課金は「より楽しむため」
失敗4: AIの返答がつまらない
→ 解決策: プロンプトを工夫。ペットらしい可愛い返答に調整
失敗5: すぐに飽きられる
→ 解決策: 毎週新しいイベント、季節限定アクセサリーで飽きさせない
→ 解決策: 許可なしでも遊べるモードを用意。後から許可を促す
失敗2: ステータス変化が激しすぎる
→ 解決策: 数時間放置してもペットは死なない。ゆるい設計に
失敗3: 課金圧が強すぎる
→ 解決策: 無課金でも十分楽しめる。課金は「より楽しむため」
失敗4: AIの返答がつまらない
→ 解決策: プロンプトを工夫。ペットらしい可愛い返答に調整
失敗5: すぐに飽きられる
→ 解決策: 毎週新しいイベント、季節限定アクセサリーで飽きさせない
