島根県安来市のフリーランスエンジニア_プログラマー画像1
AIペット飼育アプリの作り方|リアル生活連動型の完全実装ガイド – Eatransform

AIペット飼育アプリの作り方|リアル生活連動型の完全実装ガイド

🚧 デモシステム

🐶 PetLife AI

あなたの生活習慣がペットに影響する、リアル連動型AIペット

体力
85
幸福度
90
愛情度
75
🐕
元気いっぱい!
📊 今日のあなた
👟 歩数 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ヶ月で回収可能
✅ 成功させるための重要ポイント
1. リアル連動の「驚き」を作る
• 「今日たくさん歩いたからペットが元気!」という実感
• 寝不足の日はペットも元気ない、というリアルさ
• 公園に行ったらペットが喜ぶ、という偶然の発見
• この「リアルとの繋がり」が最大の差別化

2. 「本当に生きてる感」の演出
• AI会話で飼い主を覚えて成長する
• 時間帯で行動が変わる(朝は散歩、夜は寝る)
• 無視すると拗ねる、構うと喜ぶ
• 通知で「寂しいよ」「お腹空いた」と呼びかける

3. 健康促進アプリとして訴求
• 「ペットのために運動しよう」と動機づけ
• 実際に歩数が増える→ユーザーの健康改善
• メディアに「健康アプリ」として取り上げてもらう
• App Storeの「ヘルスケア」カテゴリで上位狙う

4. 感情移入させる仕掛け
• 名前をつけさせる、誕生日を設定
• 成長過程を写真で保存、思い出アルバム
• 「○○日一緒に過ごしたよ」と節目を祝う
• 老犬になったら切ない演出(泣かせる)

5. SNS拡散を狙う
• ペットの写真をシェアしやすく
• 「今日のペットの一言」を自動生成
• レアな見た目のペットは自慢したくなる
• ハッシュタグキャンペーンで口コミ拡散
❌ よくある失敗パターン
失敗1: HealthKit連携が面倒で離脱
→ 解決策: 許可なしでも遊べるモードを用意。後から許可を促す

失敗2: ステータス変化が激しすぎる
→ 解決策: 数時間放置してもペットは死なない。ゆるい設計に

失敗3: 課金圧が強すぎる
→ 解決策: 無課金でも十分楽しめる。課金は「より楽しむため」

失敗4: AIの返答がつまらない
→ 解決策: プロンプトを工夫。ペットらしい可愛い返答に調整

失敗5: すぐに飽きられる
→ 解決策: 毎週新しいイベント、季節限定アクセサリーで飽きさせない

ステルス録音アプリの作り方|完全偽装×高音質録音の実装ガイド

WordPressでTwitterハッシュタグ設定 | 自動投稿アドオン Pro