ð¶ PetLife AI
ããªãã®çæŽ»ç¿æ £ããããã«åœ±é¿ããããªã¢ã«é£ååAIããã
ãªã¢ã«ç掻é£ååAIãããã¢ããªã®æ§ç¯æé
HealthKit飿º à AIåŠç¿ à ãªã¢ã«ã¿ã€ã åå¿ã·ã¹ãã
ãªã¢ã«ç掻ããŒã¿ãååŸããæè¡éžå®
- éçºèšèª: 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
# 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
飌ãäž»ã®çæŽ»ç¿æ £ããŒã¿ããªã¢ã«ã¿ã€ã ååŸ
- æ©æ°ããŒã¿: æ¯æ¥ã®æ©æ°ãååŸãç®æšæ©æ°èšå®
- ç¡ç ããŒã¿: 就坿å»ãèµ·åºæå»ãç¡ç æé
- éåããŒã¿: ã©ã³ãã³ã°ããµã€ã¯ãªã³ã°ãªã©
- å¿ææ°: Apple WatchããååŸïŒãªãã·ã§ã³ïŒ
- ããã¯ã°ã©ãŠã³ãååŸ: ã¢ããªãéããŠãèªåæŽæ°
- ãã©ã€ãã·ãŒé æ ®: ãŠãŒã¶ãŒã®èš±å¯å¿ é
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èš±å¯ã®ã³ã: ãªããã®ããŒã¿ãå¿ èŠãªã®ãããããã®æé·ã«ã©ã圱é¿ããããæç¢ºã«èª¬æãããšãèš±å¯çãå€§å¹ ã«åäžããŸãã
ãªã¢ã«ããŒã¿ã«åºã¥ããŠããããåçã«å€å
- ã¹ããŒã¿ã¹ç®¡ç: äœåã幞çŠåºŠãææ åºŠãç©ºè ¹åºŠ
- æéçµéã·ã¹ãã : ãªã¢ã«ã¿ã€ã ã§æ°å€ãå€å
- 圱é¿èšç®: æ©æ°ãå€ãâäœå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 "å
æ°ã ãïŒ"
}
}
}
â ïž ãã©ã³ã¹èª¿æŽ: ã¹ããŒã¿ã¹ã®å€åãæ¿ãããããšãŠãŒã¶ãŒãã¹ãã¬ã¹ãæããŸããã²ãŒã ãã¶ã€ããŒãšçžè«ããŠãé©åãªãã©ã³ã¹ãèŠã€ããŸãããã
å€åºå ã§ããããåå¿ããä»çµã¿
- å ¬åæ€ç¥: å ¬åã«è¡ããšããããåã¶
- ãããã·ã§ããæ€ç¥: ããã€ãè²·ã£ãŠããããã
- åç©ç 颿€ç¥: å¥åº·èšºæã€ãã³ãçºç
- 倩æ°é£å: éšã®æ¥ã¯å®€å éã³ãæŽãã¯æ£æ©
- æé垯é£å: æã¯æ£æ©ãå€ã¯å¯ãæé
- å£ç¯ã€ãã³ã: æ¥ã¯è±èŠãå€ã¯æµ·ãå¬ã¯éªéã³
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ã䜿ã£ãŠãå®éã®å ¬åã«ãããã衚瀺ããæ©èœã远å ãããšãããã«æ²¡å ¥æãé«ãŸããŸããããããšäžç·ã«æ£æ©ããŠããæèŠãå³ãããŸãã
飌ãäž»ãšã®äŒè©±ã§ããããè³¢ããªã
- é³å£°èªè: 飌ãäž»ã®å£°ãèªèããŠåå¿
- èªç¶èšèªçè§£: OpenAI GPTã§äŒè©±ãçè§£
- æ§æ ŒåŠç¿: 話ãããæ¹ã§ãããã®æ§æ Œãå€å
- ææ åæ: 声ã®ããŒã³ããææ ãèªã¿åã
- èšæ¶ã·ã¹ãã : éå»ã®äŒè©±ãèŠããŠãã
- é³å£°åæ: ãããã話ãïŒå¯æã声ã§ïŒ
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åãç¡ææ ã䜿ãåã£ããŠãŒã¶ãŒã«ã¯èª²éãã©ã³ãæç€ºããŸãããã
æéçµéãšè²ãŠæ¹ã§èŠãç®ãå€ãã
- æé·æ®µé: åç¬ â æç¬ â èç¬ïŒãªã¢ã«æ¥æ°ã§ïŒ
- æ¯è²å€å: å¥åº·çã«è²ãŠããšç¶ºéºãªæ¯è²ã«
- äœåå€å: éåäžè¶³ã§å€ªããéåã§åŒãç· ãŸã
- ã¢ã¯ã»ãµãªãŒ: éŠèŒªãæãåžœåãªã©ã«ã¹ã¿ãã€ãº
- ã¬ã¢ã«ã©ãŒ: ç¹å¥ãªè²ãŠæ¹ã§çããè²ã«
- é²åã·ã¹ãã : æ¡ä»¶éæã§ç¹å¥ãªèŠãç®ã«
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!
)
}
}
ð¡ ã¬ãã£èŠçŽ : ã¬ã¢ãªã¢ã¯ã»ãµãªãŒãã¬ãã£ã§å ¥æã§ããä»çµã¿ã远å ãããšãåçåã«ç¹ãããŸãããã ããé床ãªèª²éèªå°ã¯é¿ããŸãããã
åéã®ããããšéã¹ãæ©èœ
- ããã°ã©ã³æ©èœ: ä»ã®ãŠãŒã¶ãŒã®ããããšéã¶
- æ£æ©ãããã³ã°: è¿ãã®ããããšæ£æ©
- åçå ±æ: ãããã®åçã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
}
â ïž ãã©ã€ãã·ãŒä¿è·: äœçœ®æ å ±ã®å ±æã¯ä»»æã«ãããŠãŒã¶ãŒãå®å¿ããŠäœ¿ããããé æ ®ããŸããããåäŸã䜿ãå Žåã¯ä¿è·è ã®åæãåŸãä»çµã¿ãå¿ èŠã§ãã
æç¶å¯èœãªããžãã¹ã¢ãã«ã®æ§ç¯
- åºæ¬ç¡æ: 1å¹ç®ã®ãããã¯ç¡æã§é£Œãã
- ãã¬ãã¢ã äŒå¡: æé¡480åã§è€æ°é£Œè²ãç¹å ž
- ã¢ã€ãã 課é: ã¬ã¢ãªã¢ã¯ã»ãµãªãŒãããã€
- åºååç: åç»åºåã§ã³ã€ã³ç²åŸ
- ãµãã¹ã¯ãªãã·ã§ã³: Apple App Store決æž
- 審æ»å¯Ÿç: ã¬ã€ãã©ã€ã³éµå®ã説æå å®
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)
}
}
}
# 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å¹é£Œè²ããŠããç¶æ ã§ ãã¹ãå¯èœã§ãã
ð¡ 審æ»ééã®ã³ã: HealthKitã®äœ¿çšç®çãæç¢ºã«èª¬æãããã²ãŒã ã®ã®ããã¯ãã§ã¯ãªãããŠãŒã¶ãŒã®å¥åº·ä¿é²ããåé¢ã«åºããšå¯©æ»ãéãããããªããŸãã
æé¡480å à 5,000人ïŒèª²éç15%ïŒ = æé売äž240äžå
æé¡480å à 20,000人ïŒèª²éç20%ïŒ = æé売äž1,920äžå
ã¢ã€ãã 課é売äžïŒæ50äžåïŒãå«ãããšãåææè³ã6ã12ã¶æã§ååå¯èœ
⢠ã仿¥ããããæ©ããããããããå æ°ïŒããšãã宿
⢠å¯äžè¶³ã®æ¥ã¯ããããå æ°ãªãããšãããªã¢ã«ã
â¢ å ¬åã«è¡ã£ããããããåã¶ããšããå¶ç¶ã®çºèŠ
⢠ãã®ããªã¢ã«ãšã®ç¹ããããæå€§ã®å·®å¥å
2. ãæ¬åœã«çããŠãæãã®æŒåº
⢠AIäŒè©±ã§é£Œãäž»ãèŠããŠæé·ãã
⢠æé垯ã§è¡åãå€ããïŒæã¯æ£æ©ãå€ã¯å¯ãïŒ
⢠ç¡èŠãããšæãããæ§ããšåã¶
⢠éç¥ã§ãå¯ããããããè ¹ç©ºããããšåŒã³ããã
3. å¥åº·ä¿é²ã¢ããªãšããŠèšŽæ±
⢠ããããã®ããã«éåãããããšåæ©ã¥ã
⢠å®éã«æ©æ°ãå¢ããâãŠãŒã¶ãŒã®å¥åº·æ¹å
⢠ã¡ãã£ã¢ã«ãå¥åº·ã¢ããªããšããŠåãäžããŠããã
⢠App Storeã®ããã«ã¹ã±ã¢ãã«ããŽãªã§äžäœçã
4. ææ ç§»å ¥ããã仿ã
⢠ååãã€ãããããèªçæ¥ãèšå®
⢠æé·éçšãåçã§ä¿åãæãåºã¢ã«ãã
⢠ãââæ¥äžç·ã«éããããããšç¯ç®ãç¥ã
⢠èç¬ã«ãªã£ããåãªãæŒåºïŒæ³£ãããïŒ
5. SNSæ¡æ£ãçã
⢠ãããã®åçãã·ã§ã¢ãããã
⢠ã仿¥ã®ãããã®äžèšããèªåçæ
⢠ã¬ã¢ãªèŠãç®ã®ãããã¯èªæ ¢ããããªã
⢠ããã·ã¥ã¿ã°ãã£ã³ããŒã³ã§å£ã³ãæ¡æ£
â 解決ç: èš±å¯ãªãã§ãéã¹ãã¢ãŒããçšæãåŸããèš±å¯ãä¿ã
倱æ2: ã¹ããŒã¿ã¹å€åãæ¿ãããã
â 解決ç: æ°æéæŸçœ®ããŠããããã¯æ»ãªãªãããããèšèšã«
倱æ3: 課éå§ã匷ããã
â 解決ç: ç¡èª²éã§ãå忥œãããã課éã¯ãããæ¥œããããã
倱æ4: AIã®è¿çãã€ãŸããªã
â 解決ç: ããã³ããã工倫ããããããã坿ãè¿çã«èª¿æŽ
倱æ5: ããã«é£œãããã
â 解決ç: æ¯é±æ°ããã€ãã³ããå£ç¯éå®ã¢ã¯ã»ãµãªãŒã§é£œããããªã
