top of page
검색

iOS Swift에서 온디바이스 AI & NLP를 활용한 게임 몰입감 향상

  • Matt
  • 4일 전
  • 4분 분량

ChatGPT 및 개발자의 코딩을 돕는 다양한 도구에 대한 논의가 많습니다. 이러한 도구는 매우 유용하지만, AI를 활용하여 앱과 게임의 몰입감을 향상시키는 방법을 공유하고 싶습니다.


또한, 이러한 기능이 완전히 온디바이스에서 실행될 수 있음을 보여드리고 싶습니다. OpenAI와 같은 서드파티 서비스는 강력한 AI 기능을 제공하지만, 최신 운영 체제에 내장된 AI 기능도 점점 강력해지고 있습니다. 이제 많은 사용 사례가 온디바이스에서 완전히 처리될 수 있습니다. 아래에서는 iOS 및 SwiftUI를 사용한 예제를 살펴보겠습니다. 바로 코드를 보고 싶다면 여기를 클릭하세요.


AI를 활용한 텍스트 기반 어드벤처 게임 향상


이번 예제에서는 텍스트 기반 어드벤처 게임을 다룹니다. 기존의 포인트 앤 클릭 방식의 어드벤처 게임처럼, 플레이어는 여러 가지 선택지를 제공받고 하나를 선택해야 합니다. 이 방식은 수십 년 동안 효과적으로 사용되었으며, 플레이어가 선택을 내리면서 게임의 이야기를 만들어 가는 경험을 제공합니다. 이러한 선택의 자유는 몰입감과 참여도를 높이는 요소가 됩니다.


하지만 몇 가지 한계가 있습니다. 기존 UI는 화면 크기의 제한으로 인해 미리 정의된 선택지만 제공해야 하므로, 플레이어가 선택할 수 있는 옵션이 일반적으로 3~4개로 제한됩니다.





기존 방식:


예를 들어, 당신이 탐정 게임을 플레이하면서 기차역에서 사건을 조사한다고 가정해 보세요. 기존 방식에서는 게임이 다음과 같은 고정된 선택지를 제공합니다:

  1. 선로를 조사한다

  2. 기차 객차를 조사한다

  3. 매표소로 간다


이 방식은 작동하지만, 플레이어의 자유도를 제한합니다.


더 몰입감 있는 AI 기반 접근법:


AI를 활용하면 더 역동적인 경험을 만들 수 있습니다. 미리 정의된 선택지를 제공하는 대신, 플레이어가 원하는 행동을 자유롭게 입력할 수 있습니다. 그러면 게임은 백그라운드에서 더 다양한 선택지와 비교하여 가장 적절한 옵션을 찾습니다. 예를 들어, 기존 선택지 외에도 다음과 같은 행동이 가능합니다:

  1. 목격자에게 질문한다

  2. 기차역을 돌아다닌다

  3. 거리의 상점을 방문한다

  4. 수상한 인물을 따라간다


이러한 자유로운 입력 방식을 통해 플레이어는 제한된 선택지에서 벗어나, 게임 세계를 더 개방적이고 몰입감 있게 느낄 수 있습니다.


자연어 처리(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 클래스를 정의합니다. 다음은 이 코드의 주요 기능입니다:


주요 기능:
  1. 사전 학습된 단어 임베딩 사용

    NLEmbedding.wordEmbedding(for: .english)를 사용하여 영어 단어 임베딩 모델을 로드합니다. 이 모델에서 단어는 숫자 벡터로 표현됩니다.

  2. 구절의 평균 벡터 계산 (averageVector(for:))

    • 입력된 문장을 단어 단위로 토큰화합니다.

    • 임베딩 모델을 사용하여 각 단어를 숫자 벡터로 변환합니다.

    • 모든 단어 벡터의 평균을 계산하여 해당 구절을 나타내는 단일 벡터를 생성합니다.

  3. 가장 적절한 문장 찾기 (bestMatch(for:in:threshold:))

    • 입력된 문장과 후보 문장을 벡터로 변환합니다.

    • 코사인 유사도를 사용하여 벡터 간의 유사성을 비교합니다.

    • 유사도 임계값(기본값: 0.7)을 충족하는 경우 가장 적절한 문장을 반환합니다.

  4. 코사인 유사도 계산 (cosineSimilarity(_:_:))

    • 다차원 공간에서 벡터 간의 각도를 기반으로 유사성을 측정합니다.

    • 값이 1에 가까울수록 유사성이 높음을 의미합니다.


우리의 작업을 지원하는 방법


이 콘텐츠는 항상 무료로 제공될 것입니다. 유용하다고 생각하신다면 다른 사람들과 공유해 주세요. 또한, 우리의 앱을 다운로드하거나 책을 읽고 솔직한 리뷰를 남겨주시면 큰 도움이 됩니다. 여러분의 의견과 피드백을 언제든지 환영합니다!


Apple App Store에서 Falling Sky를 다운로드하세요: https://apps.apple.com/app/id6446787964



X(구 트위터)에서 팔로우하세요: https://x.com/_kingdomarcade

 
 
 

Comments


bottom of page