iOS Swift에서 온디바이스 AI & NLP를 활용한 게임 몰입감 향상
- Matt
- 4일 전
- 4분 분량
ChatGPT 및 개발자의 코딩을 돕는 다양한 도구에 대한 논의가 많습니다. 이러한 도구는 매우 유용하지만, AI를 활용하여 앱과 게임의 몰입감을 향상시키는 방법을 공유하고 싶습니다.
또한, 이러한 기능이 완전히 온디바이스에서 실행될 수 있음을 보여드리고 싶습니다. OpenAI와 같은 서드파티 서비스는 강력한 AI 기능을 제공하지만, 최신 운영 체제에 내장된 AI 기능도 점점 강력해지고 있습니다. 이제 많은 사용 사례가 온디바이스에서 완전히 처리될 수 있습니다. 아래에서는 iOS 및 SwiftUI를 사용한 예제를 살펴보겠습니다. 바로 코드를 보고 싶다면 여기를 클릭하세요.
AI를 활용한 텍스트 기반 어드벤처 게임 향상
이번 예제에서는 텍스트 기반 어드벤처 게임을 다룹니다. 기존의 포인트 앤 클릭 방식의 어드벤처 게임처럼, 플레이어는 여러 가지 선택지를 제공받고 하나를 선택해야 합니다. 이 방식은 수십 년 동안 효과적으로 사용되었으며, 플레이어가 선택을 내리면서 게임의 이야기를 만들어 가는 경험을 제공합니다. 이러한 선택의 자유는 몰입감과 참여도를 높이는 요소가 됩니다.
하지만 몇 가지 한계가 있습니다. 기존 UI는 화면 크기의 제한으로 인해 미리 정의된 선택지만 제공해야 하므로, 플레이어가 선택할 수 있는 옵션이 일반적으로 3~4개로 제한됩니다.
기존 방식:
예를 들어, 당신이 탐정 게임을 플레이하면서 기차역에서 사건을 조사한다고 가정해 보세요. 기존 방식에서는 게임이 다음과 같은 고정된 선택지를 제공합니다:
선로를 조사한다
기차 객차를 조사한다
매표소로 간다
이 방식은 작동하지만, 플레이어의 자유도를 제한합니다.
더 몰입감 있는 AI 기반 접근법:
AI를 활용하면 더 역동적인 경험을 만들 수 있습니다. 미리 정의된 선택지를 제공하는 대신, 플레이어가 원하는 행동을 자유롭게 입력할 수 있습니다. 그러면 게임은 백그라운드에서 더 다양한 선택지와 비교하여 가장 적절한 옵션을 찾습니다. 예를 들어, 기존 선택지 외에도 다음과 같은 행동이 가능합니다:
목격자에게 질문한다
기차역을 돌아다닌다
거리의 상점을 방문한다
수상한 인물을 따라간다
이러한 자유로운 입력 방식을 통해 플레이어는 제한된 선택지에서 벗어나, 게임 세계를 더 개방적이고 몰입감 있게 느낄 수 있습니다.
자연어 처리(NLP) 구현
자연어 처리(NLP)를 활용하면, 사용자의 입력을 분석하고 미리 정의된 행동과 매칭할 수 있습니다. AI는 입력된 텍스트의 의도를 파악한 후, 가장 적절한 선택지를 결정합니다. 만약 “기차역을 날아서 우주로 간다”와 같이 실행 불가능한 입력이 들어오면, “여기서는 그렇게 할 수 없습니다”와 같은 응답을 제공할 수도 있습니다.
하지만, 새로운 NLP 구현을 통해 사용자는 다음과 같이 입력할 수 있습니다: “철로로 뛰어내려서 더 많은 단서를 찾는다.” 이 입력은 다음 행동으로 매칭됩니다: “선로를 조사한다.”
코드 구현
이번 예제에서는, 가능한 행동을 저장하는 클래스를 정의한 후, 게임의 스토리를 표시하고 사용자의 입력을 받을 수 있는 UI를 제공합니다. 마지막으로, NLP 로직이 입력을 처리하고 가장 적절한 행동을 찾아 실행합니다.
GameTypes.swift - 예제 코드의 기본 타입
struct GameScene {
let description: String
let validActions: [String]
}
struct GameWorld {
let scenes: [String: GameScene]
}
GameEngine.swift - 게임의 세계와 장면을 로드하고 행동을 처리
class GameEngine: ObservableObject {
@Published var gameWorld: GameWorld?
@Published var currentScene: GameScene?
init() {
loadGameWorld()
}
func loadGameWorld() {
let scenes: [String: GameScene] = [
"train_station": GameScene(
description: "A crime has just been committed at the city train station! You have been tasked to solve it. You go to the station immediately and are welcomed by the police",
validActions: ["Inspect the tracks", "Examine the train car", "Go to the ticket office", "Question the witnesses", "Walk around the station", "Visit the street-level shops", "Follow a suspicious character", ...]
),
...
]
gameWorld = GameWorld(scenes: scenes)
currentScene = gameWorld?.scenes["train_station"]
print("Game world initialised successfully!")
}
func processAction(_ input: String) -> String {
guard let scene = currentScene else { return "You are lost in the void." }
if let matchedAction = TextMatcher.bestMatch(for: input, in: scene.validActions) {
return "You chose: \(matchedAction)"
} else {
return "You can’t do that here."
}
}
}
TextMatcher.swift:
import NaturalLanguage
class TextMatcher {
static let embedding = NLEmbedding.wordEmbedding(for: .english)
static func averageVector(for phrase: String) -> [Double]? {
guard let embedding = embedding else { return nil }
let tokens = phrase.lowercased().split(separator: " ").map(String.init)
let vectors = tokens.compactMap { embedding.vector(for: $0) }
guard !vectors.isEmpty else { return nil }
let vectorLength = vectors[0].count
var average = [Double](repeating: 0, count: vectorLength)
for vector in vectors {
for i in 0..<vectorLength {
average[i] += vector[i]
}
}
for i in 0..<vectorLength {
average[i] /= Double(vectors.count)
}
return average
}
static func bestMatch(for input: String, in options: [String], threshold: Double = 0.7) -> String? {
guard let inputVector = averageVector(for: input) else { return nil }
var bestScore: Double = 0.0
var bestMatch: String?
for option in options {
if let optionVector = averageVector(for: option) {
let score = cosineSimilarity(inputVector, optionVector)
if score > bestScore {
bestScore = score
bestMatch = option
}
}
}
return bestScore >= threshold ? bestMatch : nil
}
static func cosineSimilarity(_ v1: [Double], _ v2: [Double]) -> Double {
let dotProduct = zip(v1, v2).map(*).reduce(0, +)
let magnitude1 = sqrt(v1.map { $0 * $0 }.reduce(0, +))
let magnitude2 = sqrt(v2.map { $0 * $0 }.reduce(0, +))
return dotProduct / (magnitude1 * magnitude2)
}
}
이 코드는 Apple의 NaturalLanguage (NL) 프레임워크를 사용하여 단어 임베딩을 기반으로 텍스트 유사성을 비교하는 TextMatcher 클래스를 정의합니다. 다음은 이 코드의 주요 기능입니다:
주요 기능:
사전 학습된 단어 임베딩 사용
• NLEmbedding.wordEmbedding(for: .english)를 사용하여 영어 단어 임베딩 모델을 로드합니다. 이 모델에서 단어는 숫자 벡터로 표현됩니다.
구절의 평균 벡터 계산 (averageVector(for:))
• 입력된 문장을 단어 단위로 토큰화합니다.
• 임베딩 모델을 사용하여 각 단어를 숫자 벡터로 변환합니다.
• 모든 단어 벡터의 평균을 계산하여 해당 구절을 나타내는 단일 벡터를 생성합니다.
가장 적절한 문장 찾기 (bestMatch(for:in:threshold:))
• 입력된 문장과 후보 문장을 벡터로 변환합니다.
• 코사인 유사도를 사용하여 벡터 간의 유사성을 비교합니다.
• 유사도 임계값(기본값: 0.7)을 충족하는 경우 가장 적절한 문장을 반환합니다.
코사인 유사도 계산 (cosineSimilarity(_:_:))
• 다차원 공간에서 벡터 간의 각도를 기반으로 유사성을 측정합니다.
• 값이 1에 가까울수록 유사성이 높음을 의미합니다.
우리의 작업을 지원하는 방법
이 콘텐츠는 항상 무료로 제공될 것입니다. 유용하다고 생각하신다면 다른 사람들과 공유해 주세요. 또한, 우리의 앱을 다운로드하거나 책을 읽고 솔직한 리뷰를 남겨주시면 큰 도움이 됩니다. 여러분의 의견과 피드백을 언제든지 환영합니다!
Apple App Store에서 Falling Sky를 다운로드하세요: https://apps.apple.com/app/id6446787964
X(구 트위터)에서 팔로우하세요: https://x.com/_kingdomarcade
Comments