Upgrade to Pro — share decks privately, control downloads, hide ads and more …

サーバなしで対戦ゲームが作れる!? 純正フレームワークで実現するリアルタイム通信

サーバなしで対戦ゲームが作れる!? 純正フレームワークで実現するリアルタイム通信

「アプリにリアルタイム通信を導入したいけれど、サーバ構築の学習コストや使用するライブラリ選定に不安がある」──そんな理由で導入をためらった経験はありませんか?
iOSアプリエンジニアの私もゲーム開発で同じ壁にぶつかりましたが、「標準技術であるApple純正フレームワークだけでリアルタイム対戦アクションゲームを作り上げる」という道を選びました。
そして今、 Wi-Fiなどを利用したオフライン環境での端末間通信技術への関心が高まっており、インターネット接続に頼らないリアルタイム連携の可能性が広がっています。

このトークでは、私が個人開発したSpriteKit製の対戦アクションゲームを題材に、Apple純正技術を活用したリアルタイム同期の構成や設計の工夫を紹介します。

- GameKit × GameCenterを用いたオンライン対戦のマッチングと通信設計
- MultipeerConnectivityを用いた近くの端末とのオフライン通信の構築
- 通信方式の通信範囲、レイテンシなどの比較と選定ポイント
- アクションゲームにおけるプレイヤー座標やダメージ処理を例にしたデータ同期設計

また、トークでは実際のゲームを用いたデモを通して、通信方式の違いがユーザー体験に与える違いをご覧いただけます。
「サーバ構築せず、Apple純正フレームワークだけでここまでできるのか!」という驚きとともに、「自分のアプリにもリアルタイム通信を組み込みたい」と感じてもらえるような、実践的なトークをお届けします。

Avatar for Ryutaro Okamoto / kuromelon257

Ryutaro Okamoto / kuromelon257

September 20, 2025
Tweet

More Decks by Ryutaro Okamoto / kuromelon257

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ • Ԭຊ ཾଠ࿠ / ͘Ζϝϩϯ • iOS Engineer /

    Indie Game Developer • 𝕏 : @kuromelon257 • ొஃ: #iOSDC2024 #Chiba.swift • ύʔιφϧϘΠεͰ੠༏ʹͳΖ͏ • ग़ల: ͓͔΋ͱ ΓΎ͏ͨΖ͏ ※ ຊτʔΫ͸ݸਓͷݟղͰ͋Γɺॴଐ͢Δ૊৫ͷҙݟΛ୅ද͢Δ΋ͷͰ͸͋Γ·ͤΜɻ
  2. ໨࣍ 1. ϦΞϧλΠϜ௨৴͕Ͱ͖Δ७ਖ਼ϑϨʔϜϫʔΫ঺հ 1. ۙ͘ͷਓͱखܰʹ Multipeer Connectivity 2. iOS 26͔Βͷ৽ػೳ

    Wi ‑ Fi Aware 3. ΦϯϥΠϯରઓͷϕετΞϯαʔ GameKit 2. P2P ௨৴ʹ͓͚Δσʔλಉظख๏ 1. ͳͥσʔλಉظ͕ඞཁͳͷ͔ʁ 2. ୅දख๏ͷ঺հ 3. ΞΫγϣϯήʔϜ޲͚ͷख๏ͷఏҊ
  3. Multipeer Connectivityͱ͸ʢiOS 7+ʣ ۙ͘ͷ୺຤ಉ࢜Λ Bluetooth, Wi-FiͰ઀ଓ Bluetooth, Wi-Fi͔Β ࠷దͳํࣜΛࣗಈબ୒ [ࢀߟ]

    Multipeer Connectivity | Apple Developer Documentation: https://developer.apple.com/documentation/multipeerconnectivity
  4. // 1) ଘࡏࠂ஌ɾ୺຤୳ࡧͷ։࢝ let peerID = MCPeerID(displayName: "૬खʹද໊͍ࣔͤͨ͞લ") let advertiser

    = MCNearbyServiceAdvertiser(peer: peerID, ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹdiscoveryInfo: nil, ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹserviceType: "my-game") let browser = MCNearbyServiceBrowser(peer: peerID, serviceType: "my-game") advertiser.delegate = self browser.delegate = self advertiser.startAdvertisingPeer() // ࣗ෼ͷଘࡏΛࠂ஌ browser.startBrowsingForPeers() // ଞ୺຤Λ୳ࡧ Multipeer Connectivity ͷ࠷খ࣮૷खॱ: ୺຤ݕࡧʢ1/3ʣ
  5. Multipeer Connectivity ͷ࠷খ࣮૷खॱ: ୺຤ݕࡧʢ2/3ʣ // 2) σʔλૹ৴ let session =

    MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) let gameData = "player_moved".data(using: .utf8)! try session.send(gameData, toPeers: session.connectedPeers, with: .unreliable) MCPeerID ͷ഑ྻͰ ର৅ΛࢦఆՄೳ
  6. Multipeer Connectivity ͷ࠷খ࣮૷खॱ: ୺຤ݕࡧʢ2/3ʣ TCP • ৴པੑॏࢹɿॱংอূɾ౸ୡ֬ೝʢACKʣɾ࠶ૹ͋Γʹਖ਼֬ʹಧ͘ • ༻్ɿ՝ۚɾϩάΠϯɾAPIʗϑΝΠϧసૹͳͲऔΓ͜΅͠NGͷ௨৴ UDP

    • ଎౓ɾܰྔॏࢹɿॱংอূͳ͠ɾ౸ୡ֬ೝͳ͠ɾ࠶ૹͳ͠ʹTCPΑΓ଎͍ • ༻్ɿΦϯϥΠϯήʔϜʗԻ੠ɾө૾഑৴ͳͲ௿ϨΠςϯγ͕࠷༏ઌ
  7. Multipeer Connectivity ͷ࠷খ࣮૷खॱ: ୺຤ݕࡧʢ3/3ʣ // 3) σʔλड৴ʢσϦήʔτϝιουʣ func session(_ session:

    MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { Task { @MainActor in let message = String(data: data, encoding: .utf8) ?? "" self.updateGameState(message) } }
  8. Multipeer Connectivity ࠾༻ͷϝϦοτ / σϝϦοτ ϝϦοτ σϝϦοτ w ϖΞϦϯάͷਫ਼౓΋ߴ࣮͘૷΋؆୯ w

    ෳ਺୆઀ଓΛαϙʔτ ɾ.$4FTTJPO͕ෳ਺ͷQFFSΛ಺แ w Πϯλʔωοτෆཁ ɾలࣔɾڭҭݱ৔ʹ࠷ద ɾෆఆظʹόʔετൃੜ ɾΧΫπΩ͋Γ
  9. Wi ‑ Fi Aware ͱ͸ʢiOS 26+ʣ ۙ઀୺຤ಉ͕࢜ ௚઀ൃݟɾ઀ଓ ߴεϧʔϓοτ ௿ϨΠςϯγ

    ϖΞϦϯά৘ใ͕ ୺຤ʹӬଓอଘ [ࢀߟ] Wi-Fi Aware | Apple Developer Documentation: https://developer.apple.com/documentation/WiFiAware
  10. Wi ‑ Fi Aware ͷ࠷খ࣮૷: ΤϯλΠτϧϝϯτ & αʔϏεએݴʢ1/4ʣ // Info.plist

    ઃఆ <key>com.apple.developer.wifi-aware</key> <array> <string>Publish</string> // ଞͷ୺຤͔Βͷ઀ଓΛड͚ೖΕΔݖݶ <string>Subscribe</string> // ଞͷ୺຤ʹ઀ଓ͠ʹߦ͘ݖݶ </array> <key>WiFiAwareServices</key> <dict> <key>_my-app._udp</key> <dict> <key>Publishable</key><dict/> // ଴ػՄೳ <key>Subscribable</key><dict/> // ݕࡧՄೳ </dict> </dict>
  11. Wi ‑ Fi Aware ͷ࠷খ࣮૷: ઀ଓ଴ػ༻UIʢDeviceDiscoveryUIʣʢ2/4ʣ import DeviceDiscoveryUI // ૬ख͔Βͷ઀ଓΛ଴ػ͢ΔϏϡʔ

    DevicePairingView( .wifiAware( .connecting( to: .gameService, from: .userSpecifiedDevices ) ) ) { Text("ϖΞϦϯά଴ػத...") } fallback: { Text("Wi-Fi Awareར༻ෆՄ") }
  12. Wi ‑ Fi Aware ͷ࠷খ࣮૷: ݕࡧ༻UIʢDeviceDiscoveryUIʣʢ3/4ʣ import DeviceDiscoveryUI // पғͷσόΠεΛݕࡧ

    DevicePicker(.wifiAware(.connecting(to: .userSpecifiedDevices, from: .gameService))) { endpoint in // ϖΞϦϯά׬ྃɺ࣍ͷ௨৴εςοϓ΁ self.startGameCommunication(with: endpoint) } label: { Text("σόΠεΛ୳͢") } fallback: { Text("Wi-Fi Awareར༻ෆՄ") }
  13. Wi ‑ Fi Aware ͷ࠷খ࣮૷: σʔλͷૹड৴ʢ4/4ʣ import WiFiAware import Network

    // Network framework઀ଓ let connection = NetworkConnection(to: endpoint, using: .parameters { UDP() // ௿஗Ԇ༏ઌ }.wifiAware { $0.performanceMode = .realtime // ϦΞϧλΠϜ࠷దԽ }) // σʔλૹ৴ let gameData = "player_moved_x:100_y:200".data(using: .utf8)! try await connection.send(gameData) // σʔλड৴ for try await (data, _) in connection.messages { let message = String(data: data, encoding: .utf8) ?? “” } ୯ҰσόΠεͱͷίωΫγϣϯ
  14. Wi ‑ Fi Aware ࠾༻ͷϝϦοτ / σϝϦοτ ϝϦοτ σϝϦοτ w

    ௿஗Ԇɺόʔετͳ͠Ͱ҆ఆ w ϖΞϦϯά6*͕ඪ४౥ࡌ w 04ʹϖΞϦϯά৘ใ͕֨ೲ w ରԠσόΠεݶఆ ɾJ1IPOF ɺJ04  w ෳ਺୆઀ଓ͸ࣗ෼Ͱ࣮૷ ɾϒϩʔυΩϟετ͸ΞϓϦ૚Ͱ࣮ݱ w ϦϦʔεͯؒ͠΋ͳ͍ ɾࢿྉ͕গͳ͍ ɾॳظͷ༧ظ͠ͳ͍ෆ۩߹ϦεΫ
  15. GameKit ͱ͸ (iOS 3+) Game CenterͰ ϩάΠϯɺϑϨϯυ؅ཧ ΦϯϥΠϯରઓΛ ؆୯࣮૷ Ϛονϯάɾ௨৴

    ೝূ͕ҰମԽ ηʔϒɾϥϯΩϯά ࣮੷ػೳ΋౥ࡌ [ࢀߟ] GameKit | Apple Developer Documentation: https://developer.apple.com/documentation/gamekit
  16. GameKit ͷ࠷খ࣮૷: Game Center ೝূʢ1/3ʣ import GameKit // Game Centerೝূ

    GKLocalPlayer.local.authenticateHandler = { [weak self] vc, error in if let vc = vc { self?.transitionDelegate?.transitionToVC(vc: vc) } else if GKLocalPlayer.local.isAuthenticated { print("✅ ೝূ੒ޭ: \(GKLocalPlayer.local.gamePlayerID)") } else { print("❌ ೝূࣦഊ: \(String(describing: error))") } }
  17. GameKit ͷ࠷খ࣮૷: Ϛονϯά։࢝ʢ2/3ʣ let request = GKMatchRequest() request.minPlayers = 2

    // ࢀՃ࠷௿2ਓ request.maxPlayers = 2 // ࢀՃ࠷େ2ਓ request.playerGroup = 12345 // ಉ͡άϧʔϓͱ͚ͩϚονϯά // ඪ४UIΛ࢖͏৔߹ if let matchmakerVC = GKMatchmakerViewController(matchRequest: request) { matchmakerVC.matchmakerDelegate = self matchmakerVC.matchmakingMode = .default transitionDelegate?.transitionToVC(vc: matchmakerVC) } // ඪ४UIΛ࢖༻͠ͳ͍৔߹ GKMatchmaker.shared().findMatch(for: request) { match, error in match.delegate = self }
  18. GameKit ͷ࠷খ࣮૷: σʔλૹड৴ʢ3/3ʣ // σʔλૹ৴ let gameData = "player_moved_x:100_y:200".data(using: .utf8)!

    // ϦΞϧλΠϜૹ৴ʢUnreliableʣ try match.sendData(toAllPlayers: data, with: .unreliable) // σʔλड৴ʢGKMatchDelegateͷσϦήʔτϝιουʣ func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) { Task { @MainActor in let message = String(data: data, encoding: .utf8) ?? "" self.updateGameState(message) } } ର৅ΛࢦఆՄೳ
  19. GameKit ࠾༻ͷϝϦοτ / σϝϦοτ ϝϦοτ σϝϦοτ w ໨ཱͬͨ஗Ԇͳ͠ w Ϛονϯάɾೝূɾ௨৴͕

    ΦʔϧΠϯϫϯ w αʔόʔඅ༻θϩ w ӡ༻ෛՙͳ͠ ੑೳ͕Πϯλʔωοτ؀ڥʹґଘ
  20. 3ख๏ͷൺֱ Multipeer Connectivity Wi ‑ Fi Aware GameKit ରԠόʔδϣϯ ϓϥοτϑΥʔϜ

    σόΠε౳ J04  J1BE04ʗNBD04౳ 8J'J#MVFUPPUI౥ࡌػ J04  J04ͱJ1BEͷΈ J1IPOF ͳͲݶఆ J04  J04ʗJ1BE04ʗNBD04ʗUW04 ෯޿͘ରԠ ௨৴ൣғ ΦϑϥΠϯ #MVFUPPUIʗಉҰ-"/ʗ 118J'J ΦϑϥΠϯ 8J'J૬౰ʢ਺ेNʣ ΦϯϥΠϯ Πϯλʔωοτલఏ ڑ཭੍ݶͳ͠ ࣮૷೉қ౓ ௿ ɾඪ४6*͋Γ ɾෳ਺୆઀ଓαϙʔτ தʙߴ ɾඪ४6*͋Γ ɾ௨৴͚ͩ/FUXPSLϑϨʔϜϫʔΫ ɾෳ਺୆઀ଓ͸࣮૷ͷඞཁ͋Γ ௿ ɾඪ४6*͋Γ ɾෳ਺୆઀ଓαϙʔτ ҆ఆੑ ΍΍ෆ҆ఆ ɾόʔετͷՄೳੑ͋Γ ҆ఆ Πϯλʔωοτ؀ڥʹґଘ
  21. ໨࣍ 1. ϦΞϧλΠϜ௨৴͕Ͱ͖Δ७ਖ਼ϑϨʔϜϫʔΫ঺հ 1. ۙ͘ͷਓͱखܰʹ Multipeer Connectivity 2. iOS 26͔Βͷ৽ػೳ

    Wi ‑ Fi Aware 3. ΦϯϥΠϯରઓͷϕετΞϯαʔ GameKit 2. P2P ௨৴ʹ͓͚Δσʔλಉظख๏ 1. ͳͥσʔλಉظ͕ඞཁͳͷ͔ʁ 2. ୅දख๏ͷ঺հ 3. ΞΫγϣϯήʔϜ޲͚ͷख๏ͷఏҊ
  22. ୅දతͳಉظख๏ͷ঺հʢϑϧϝογϡܕʣ B C A ඳ ը 1ϑϨʔϜ໨ ߈ܸϘλϯ μογϡϘλϯ ॲཧ

    δϟϯϓϘλϯ ॲཧ ॲཧ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  23. ୅දతͳಉظख๏ͷ঺հʢϑϧϝογϡܕʣ B C A 1ϑϨʔϜ໨ 2ϑϨʔϜ໨ ඳ ը μογϡϘλϯ δϟϯϓϘλϯ

    ߈ܸϘλϯ ॲཧ ॲཧ ॲཧ ඳ ը ߈ܸϘλϯ μογϡϘλϯ ॲཧ δϟϯϓϘλϯ ॲཧ ॲཧ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  24. ୅දతͳಉظख๏ͷ঺հʢϑϧϝογϡܕʣ B C A 1ϑϨʔϜ໨ 2ϑϨʔϜ໨ 3ϑϨʔϜ໨ … ඳ ը

    μογϡϘλϯ δϟϯϓϘλϯ ߈ܸϘλϯ ॲཧ ॲཧ ॲཧ ඳ ը ߈ܸϘλϯ μογϡϘλϯ ॲཧ δϟϯϓϘλϯ ॲཧ ॲཧ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  25. ୅දతͳಉظख๏ͷ঺հʢελʔܕʣ B Client C Client A Host →Ϙλϯ δϟϯϓϘλϯ શһͷ࠲ඪͳͲ

    શһͷ࠲ඪͳͲ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  26. ୅දతͳಉظख๏ͷ঺հʢελʔܕʣ B Client C Client A Host ඳ ը 1ϑϨʔϜ໨

    ߈ܸϘλϯ →Ϙλϯ δϟϯϓϘλϯ ॲཧ શһͷ࠲ඪͳͲ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  27. ୅දతͳಉظख๏ͷ঺հʢελʔܕʣ B Client C Client A Host ඳ ը 1ϑϨʔϜ໨

    ߈ܸϘλϯ →Ϙλϯ δϟϯϓϘλϯ ॲཧ 2ϑϨʔϜ໨ ඳ ը →Ϙλϯ ߈ܸϘλϯ ←Ϙλϯ ॲཧ … શһͷ࠲ඪͳͲ શһͷ࠲ඪͳͲ [ࢀߟ] ॳ৺ऀͰ΋Θ͔ΔʂʮΦϯϥΠϯήʔϜʹ͓͚Δ࢓૊ΈʯΛֶΜͰΈΑ͏ʲޙฤʳ: https://qiita.com/4_mio_11/items/0f3444e43548df417fe6
  28. ୅දతͳಉظख๏ͷൺֱ ϝϦοτ σϝϦοτ ϑϧϝογϡܕ ࣗ෼ͷ৘ใΛશମʹૹ৴͢ΔͷͰɺ ϗετͷੑೳʹࠨӈ͞Εͳ͍ • શ୺຤͕ಉ͡ॲཧΛ͠ͳ͍ͱ ಉظʹͳΒͳ͍ •

    ϥϯμϜཁૉΛऔΓೖΕʹ͍͘ ελʔܕ ϗετ͕ҰׅͰॲཧ͢ΔͨΊɺ σʔλ͸Ұҙʹ؅ཧͰ͖Δ • ϗετͷॲཧ͕ऴΘ͔ͬͯΒ Ͱͳ͍ͱඳըͰ͖ͳ͍ʢ஗Ԇʣ • ϗετͷ཭୤ͰήʔϜਐߦ͕ఀࢭ
  29. ߟҊͨ͠σʔλಉظख๏ɿϋΠϒϦουܕ B Client C Client A Host ϓϨΠϠʔB ͷঢ়ଶ ɾঢ়ଶ:

    ߈ܸத ɾX࠲ඪ: 300 ɾY࠲ඪ: 200 ϓϨΠϠʔB ͷঢ়ଶ ɾঢ়ଶ: ߈ܸத ɾX࠲ඪ: 300 ɾY࠲ඪ: 200 ϓϨΠϠʔAͷঢ়ଶ ɾঢ়ଶ: ඃ஄த ɾX࠲ඪ: 100 ɾY࠲ඪ: 300 ήʔϜͷঢ়ଶ ɾঢ়ଶ: Ϛονத ɾ࢒Γ࣌ؒ: 30 Πϕϯτ ɾର৅: A ɾλΠϓ: ඃ஄ ϓϨΠϠʔAͷঢ়ଶ ɾঢ়ଶ: ඃ஄த ɾX࠲ඪ: 100 ɾY࠲ඪ: 300 ήʔϜͷঢ়ଶ ɾঢ়ଶ: Ϛονத ɾ࢒Γ࣌ؒ: 30 Πϕϯτ ɾର৅: A ɾλΠϓ: ඃ஄
  30. ୅දతͳಉظख๏ͷൺֱ ϝϦοτ σϝϦοτ ϑϧϝογϡܕ ࣗ෼ͷ৘ใΛશମʹૹ৴͢ΔͷͰɺ ϗετͷੑೳʹࠨӈ͞Εͳ͍ • શ୺຤͕ಉ͡ॲཧΛ͠ͳ͍ͱ ಉظʹͳΒͳ͍ •

    ϥϯμϜཁૉΛऔΓೖΕʹ͍͘ ελʔܕ ϗετ͕ҰׅͰॲཧ͢ΔͨΊɺ σʔλ͸Ұҙʹ؅ཧͰ͖Δ • ϗετͷॲཧ͕ऴΘ͔ͬͯΒ Ͱͳ͍ͱඳըͰ͖ͳ͍ʢ஗Ԇʣ • ϗετͷ཭୤ͰήʔϜਐߦ͕ఀࢭ ϋΠϒϦουܕ • ϗετ͕ҰׅͰॲཧ͢ΔͨΊɺ σʔλ͸Ұҙʹ؅ཧͰ͖Δ • ࣗ෼ͷ৘ใΛશମʹૹ৴͢Δɺϗετ ͷੑೳʹࠨӈ͞Εͳ͍ • ஗Ԇ͸Πϕϯτॲཧ͚ͩ • ϗετͷ཭୤ͰήʔϜਐߦ͕ఀࢭ 解決 解決 解決
  31. ຊ೔ͷτʔΫ಺༰ 1. ७ਖ਼ϑϨʔϜϫʔΫΛબͿཧ༝ • ๛෋ͳબ୒ࢶ͔ͭແྉ 2. Multipeer Connectivity • Bluetooth,

    Wi-Fi Ͱ઀ଓ • ࣌ʑόʔετൃੜɺෆ҆ఆ 3. Wi ‑ Fi Aware • ଎౓ͱ҆ఆੑ͕͋Δ • iPhone12+ ͔ͭ iOS26+ • ෳ਺୆઀ଓͷ࣮૷͕ඞཁ 4. GameKit • ΦϯϥΠϯରઓͳΒ͜Ε • ੑೳ΋͋Γɺ࣮૷΋༰қ • ήʔϜͷඞཁػೳຬࡌ 5. ΦϯϥΠϯ/ΦϑϥΠϯ྆ํ ରԠ͓ͯ͘͠ͱ҆৺ 6. P2P ௨৴ʹ͸σʔλಉظ͕ඞཁ • ϑϧϝογϡܕɺελʔܕ • ͍͍఺Λ߹Θͤͨख๏΋Φεεϝ