島根県安来垂のフリヌランス゚ンゞニア_プログラマヌ画像

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