在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 游戲中的多點(diǎn)互聯(lián)
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

游戲中的多點(diǎn)互聯(lián)

多點(diǎn)互聯(lián) (Multipeer Connectivity,即 MPC) 是在 2013 年的 WDCC 中提出的,期間做過(guò)不少宣傳,但是卻很少有案例能夠成功有效地使用它。接下來(lái),就讓我們來(lái)看一看如何正確使用 MPC,尤其是在游戲中的應(yīng)用。

什么是多點(diǎn)互聯(lián)

多點(diǎn)互聯(lián)是蘋(píng)果的一個(gè)傳輸無(wú)關(guān)的網(wǎng)絡(luò)框架,提供網(wǎng)絡(luò)的發(fā)現(xiàn)、創(chuàng)建和通信功能。可以說(shuō)它是 Bonjour 的精神傳承者, Bonjour 可以在 LAN 和 Wi-Fi 的網(wǎng)絡(luò)下高效地識(shí)別設(shè)備。

MPC 的關(guān)鍵用途在于創(chuàng)建臨時(shí)網(wǎng)絡(luò)中的點(diǎn)對(duì)點(diǎn)連接,而不需要考慮天氣、無(wú)線、藍(lán)牙等各種因素,只需要有個(gè)人網(wǎng)絡(luò)就行。一旦創(chuàng)建之后,各個(gè)節(jié)點(diǎn)可以安全地共享消息、數(shù)據(jù)和文件資源。

絕大部分 MPC 的功能在更高層的 GameKit 框架中都可以找到。使用 GameKit 可以讓開(kāi)發(fā)者接觸到有用的游戲概念,抽離底層的網(wǎng)絡(luò)協(xié)議。

大部分的游戲都更適合用 GameKit 開(kāi)發(fā),它有很多直接使用 MPC 實(shí)現(xiàn)的游戲相關(guān)的封裝。不過(guò)作為 MPC 的進(jìn)階手冊(cè),本文主要涉及 MPC 的各種使用技巧。

什么時(shí)候該用

當(dāng)你的游戲或應(yīng)用需要在近距離的多臺(tái)設(shè)備中進(jìn)行連接的時(shí)候, MPC 可以大幅提高用戶體驗(yàn)。不論你是想要建立一個(gè)遠(yuǎn)程控制還是多人游戲, MPC 都可以幫助你減少用戶使用過(guò)程中的阻力,減少服務(wù)器的開(kāi)銷,甚至可以減少網(wǎng)絡(luò)延時(shí)等問(wèn)題。

比如一個(gè)遠(yuǎn)程控制的應(yīng)用,如果它不需要用戶進(jìn)行任何設(shè)置,而是在安裝后立即自動(dòng)連接到被控制端上,那么應(yīng)用的品質(zhì)會(huì)得到很大的提升。不論這個(gè)遠(yuǎn)程控制針對(duì)的是游戲、軟件展示、音頻播放還是其他東西,都是這樣。DeckRocket 就是一個(gè)很好的開(kāi)源的例子,它是一個(gè)用來(lái)遠(yuǎn)程遙控 DeckSet 幻燈片的 iOS 應(yīng)用。

多用戶游戲也可以從 MPC 的零配置和離線連接特性中受益。比如一個(gè)包含游戲邏輯、規(guī)則和存檔功能的卡牌類游戲,可以在不聯(lián)網(wǎng)的狀態(tài)下讓任意兩名玩家進(jìn)行即時(shí)對(duì)戰(zhàn)。在這篇文章里,我們將會(huì)從 CardsAgainst 這個(gè)真實(shí)的應(yīng)用中選取一些例子進(jìn)行說(shuō)明。 CardsAgainst 是著名游戲 Cards Against Humanity 的開(kāi)源 iOS 版本,完整的項(xiàng)目源代碼可以在 Github 獲取。

本文中的其他示例則選自 PeerKit,一個(gè) Github 上的開(kāi)源框架,用來(lái)構(gòu)建事件驅(qū)動(dòng)且無(wú)需配置的 MPC 應(yīng)用。

發(fā)現(xiàn)設(shè)備的相關(guān)設(shè)置

有很多種方法可以把 MPC 的設(shè)備偵測(cè)概念整合到應(yīng)用中。接下來(lái)我們將介紹三種廣泛使用的設(shè)計(jì)模式。

方法一:默認(rèn)方式

蘋(píng)果提供了一個(gè)內(nèi)置的 ViewController ,可以很方便地進(jìn)行匹配和初始化連接。只需要設(shè)置好 serviceTypesession 并且彈出一個(gè) MCBrowserViewController 即可,MPC 會(huì)幫你做好剩下的事情。注意,serviceType 最多是 15 位 ASCII 字符。使用方法通常像逆向的 DNS 標(biāo)記一樣 (例如: io-objc-mpc):

let session = MCSession(peer: MCPeerID(displayName: "Mary"))
let serviceType = "io-objc-mpc" // 最多 15 ASCII 字符
window!.rootViewController = MCBrowserViewController(serviceType: serviceType, session: session)

http://wiki.jikexueyuan.com/project/objc/images/18-9.png" alt="" />

不過(guò),我們無(wú)法輕易地對(duì) MCBrowserViewController 進(jìn)行自定義,而你有可能想設(shè)置自己的匹配原則,那么請(qǐng)移步下面的章節(jié)。

方法二:專門的公示者 (Advertiser) / 瀏覽者 (Browser)

如果你的游戲的匹配機(jī)制是先選取一個(gè)主節(jié)點(diǎn)來(lái)協(xié)調(diào)游戲邏輯,然后其他次節(jié)點(diǎn)和主節(jié)點(diǎn)進(jìn)行連接,那么你應(yīng)該充分利用這些信息,只需要從主節(jié)點(diǎn)進(jìn)行公示,然后次節(jié)點(diǎn)進(jìn)行瀏覽即可:

http://wiki.jikexueyuan.com/project/objc/images/18-10.gif" alt="" />

// 主節(jié)點(diǎn)公示
advertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: discoveryInfo, serviceType: serviceType)
advertiser.delegate = self
advertiser.startAdvertisingPeer()

// 次節(jié)點(diǎn)瀏覽
mcBrowser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: serviceType)
mcBrowser.delegate = self
mcBrowser.startBrowsingForPeers()

但是,總是有那么一些情況,最好能在應(yīng)用運(yùn)行之前就建立好連接,而不用用戶進(jìn)行任何操作。下面的章節(jié)就展示了如何實(shí)現(xiàn)這樣的功能。

方法三:零配置

MPC 能夠極大地減少用戶體驗(yàn)的阻力。當(dāng)你以正確的方式把它整合到應(yīng)用中時(shí),你的用戶可以在安裝應(yīng)用之后立即開(kāi)始通信,而不用任何配置。這會(huì)是一件大快所有人心的大好事。

http://wiki.jikexueyuan.com/project/objc/images/18-11.gif" alt="" />

為了實(shí)現(xiàn)這個(gè)功能,我們需要同時(shí)對(duì)會(huì)話進(jìn)行公示和查看,我們把這種行為稱之為 收發(fā) (transceiving = transmitting and receiving)。

在多節(jié)點(diǎn)進(jìn)行收發(fā)的時(shí)候,競(jìng)爭(zhēng)問(wèn)題是一個(gè)重大的挑戰(zhàn),因?yàn)榭赡軙?huì)有很多節(jié)點(diǎn)同時(shí)嘗試連接彼此。這便是領(lǐng)袖選舉 (Leader Election) 問(wèn)題,這個(gè)問(wèn)題已經(jīng)被深入地討論和研究,并且有一些很好地解決方案。

下面介紹一種簡(jiǎn)單而有效的方法。在邀請(qǐng)其他節(jié)點(diǎn)加入會(huì)話的時(shí)候,將每個(gè)節(jié)點(diǎn)的運(yùn)行時(shí)間包含到元數(shù)據(jù) (metadata) 里,公示的節(jié)點(diǎn)總是加入到最早的會(huì)話中:

// 瀏覽者的委托代碼
func browser(browser: MCNearbyServiceBrowser!, foundPeer peerID: MCPeerID!, withDiscoveryInfo info: [NSObject : AnyObject]!) {
    var runningTime = -timeStarted.timeIntervalSinceNow
    let context = NSData(bytes: &runningTime, length: sizeof(NSTimeInterval))
    browser.invitePeer(peerID, toSession: mcSession, withContext: context, timeout: 30)
}

// 公示者的委托代碼
func advertiser(advertiser: MCNearbyServiceAdvertiser!, didReceiveInvitationFromPeer peerID: MCPeerID!, withContext context: NSData!, invitationHandler: ((Bool, MCSession!) -> Void)!) {
    var runningTime = -timeStarted.timeIntervalSinceNow
    var peerRunningTime = NSTimeInterval()
    context.getBytes(&peerRunningTime)
    let isPeerOlder = (peerRunningTime > runningTime)
    invitationHandler(isPeerOlder, mcSession)
    if isPeerOlder {
        advertiser.stopAdvertisingPeer()
    }
}

發(fā)送和接受

MPC 提供了幾種發(fā)送和接收數(shù)據(jù)的方式,每種方式都有自己獨(dú)有的特點(diǎn)和取舍。

發(fā)送數(shù)據(jù)

當(dāng)發(fā)送少量事件驅(qū)動(dòng)的數(shù)據(jù) (最多幾 kb) 的時(shí)候,比如游戲事件 (開(kāi)始/暫停/退出),使用這個(gè)方法:sendData(_:toPeers:withMode:error:)

為了封裝傳輸?shù)臄?shù)據(jù),CardsAgainst 定義了一個(gè)游戲事件的枚舉類型,在接下來(lái)對(duì)隨行的數(shù)據(jù)進(jìn)行序列化和反序列化的時(shí)候也會(huì)用到:

// 所有的游戲事件
enum Event: String {
    case StartGame = "StartGame",
    Answer = "Answer",
    CancelAnswer = "CancelAnswer",
    Vote = "Vote",
    NextCard = "NextCard",
    EndGame = "EndGame"
}

// 可靠地 (使用 .Reliable 模式) 向節(jié)點(diǎn)發(fā)送事件,有可能有隨行數(shù)據(jù)
func sendEvent(event: Event, object: AnyObject? = nil, toPeers peers: [MCPeerID] = session.connectedPeers as [MCPeerID]) {
    if peers.count == 0 {
        return
    }
    var rootObject: [String: AnyObject] = ["event": event.rawValue]
    if let object = object {
        rootObject["object"] = object
    }
    let data = NSKeyedArchiver.archivedDataWithRootObject(rootObject)
    session.sendData(data, toPeers: peers, withMode: .Reliable, error: nil)
}

// 使用例
sendEvent(.StartGame, ["initialData": "hello objc.io!"])

具體內(nèi)容可以參考 ConnectionManager.swift 的源代碼。

可靠傳輸 和 不可靠傳輸

就像是 TCP/UDP 一樣,MPC 有可靠傳輸和不可靠傳輸兩種模式。MCSessionSendDataMode 包含了這兩種模式。

如果要在可靠模式 (.Reliable) 下發(fā)送數(shù)據(jù):

let message = "Hello objc.io!"
let data = message.dataUsingEncoding(NSUTF8StringEncoding)!
var error: NSError? = nil
if !session.sendData(data, toPeers: peers, withMode: .Reliable, error: &error) {
    println("error: \(error!)")
}

如果你發(fā)送的數(shù)據(jù)十分關(guān)鍵,直接關(guān)系到你的游戲能否正常運(yùn)行,比如開(kāi)始或者暫停游戲,使用可靠模式 (.Reliable):

如果與準(zhǔn)確性和有序性相比,速度的優(yōu)先級(jí)更高,比如發(fā)送傳感器的數(shù)據(jù),那么不可靠模式 (.Unreliable) 可能更適合。務(wù)必權(quán)衡利弊,在考慮好的情況下,選擇最適合你的方案。

發(fā)送文件

當(dāng)你發(fā)送大量數(shù)據(jù) (幾百 kB 甚至幾 MB) 的時(shí)候,比如文件,應(yīng)該使用 sendResourceAtURL(_:withName:toPeer:withCompletionHandler:) 方法。它可以通過(guò) NSProgress 對(duì)象讓發(fā)送方和接收方同時(shí)監(jiān)控傳輸進(jìn)度。

這是 DeckRocket 中的例子:

pdfProgress = session!.sendResourceAtURL(url, withName: filePath.lastPathComponent, toPeer: peer) { error in
    dispatch_async(dispatch_get_main_queue()) {
        self.pdfProgress!.removeObserver(self, forKeyPath: "fractionCompleted", context: &ProgressContext)
        if error != nil {
            HUDView.show("Error!\n\(error.localizedDescription)")
        } else {
            HUDView.show("Success!")
        }
    }
}
pdfProgress!.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &ProgressContext)

對(duì)于流數(shù)據(jù),比如傳感器的讀數(shù)或者持續(xù)更新的用戶坐標(biāo)信息等等,可以使用 startStreamWithName(_:toPeer:error:) 方法把數(shù)據(jù)寫(xiě)到 NSOutputStream 中。接收者則通過(guò) NSInputStream 讀取數(shù)據(jù)流:

// 接收者
public func session(session: MCSession!, didReceiveStream stream: NSInputStream!, withName streamName: String!, fromPeer peerID: MCPeerID!) {
    // 假設(shè)是一個(gè) UInt8 的流
    var buffer = [UInt8](count: 8, repeatedValue: 0)

    stream.open()

    // 讀取單個(gè)字節(jié)
    if stream.hasBytesAvailable {
        let result: Int = stream.read(&buffer, maxLength: buffer.count)
        println("result: \(result)")
    }
}

挑戰(zhàn)

雖然 MPC 很強(qiáng)大,但同時(shí)也面臨不少挑戰(zhàn)。下面列舉一下你可能會(huì)遇到的問(wèn)題。

可用性

MPC 只能用于 iOS 7、iOS 8 和 OS X 10.10 ,所以如果不是蘋(píng)果的設(shè)備,或者不是最新的 OS X 發(fā)行版的話,那么請(qǐng)忘了 MPC 吧。跨平臺(tái)的應(yīng)用或者游戲需要依賴別的替代品。

可靠性

盡管在 iOS 7 之后蘋(píng)果對(duì) MPC 的可靠性做了很大的提升,可靠性依舊是 MPC 的痛處。不得不考慮到連接失敗的情況,而且為了盡可能覆蓋很多邊界情況,還需要做不少額外的功課。

同步性和競(jìng)爭(zhēng)條件

撇開(kāi)因無(wú)線連接的損耗所導(dǎo)致的網(wǎng)絡(luò)延時(shí)不談,編寫(xiě)即時(shí)型網(wǎng)絡(luò)的代碼有點(diǎn)像是寫(xiě)本地的多線程代碼。在假設(shè)事件發(fā)送成功之前,務(wù)必在合適的位置對(duì)關(guān)鍵傳輸加鎖,從而確保所有節(jié)點(diǎn)確認(rèn)接收關(guān)鍵事件。

游戲常常需要共享狀態(tài),比如游戲是否開(kāi)始或暫停,玩家是否退出等等。如果玩家在對(duì)手即將發(fā)動(dòng)致命一擊的時(shí)候暫停游戲了會(huì)怎么樣? MPC 將異步的游戲邏輯競(jìng)爭(zhēng)留給開(kāi)發(fā)者來(lái)決定。使用 GameKit 這樣的框架對(duì)集中邏輯很有幫助,但是同時(shí)也犧牲了一些靈活性作為代價(jià)。

替代選擇

用 MPC 來(lái)寫(xiě)一個(gè)復(fù)雜的游戲無(wú)疑充滿了挑戰(zhàn)性。你可以了解一下其他選擇再做決定。

GameKit

蘋(píng)果在 GameKit 中投入了很多想法。盡管它強(qiáng)制要求使用指定的模型和結(jié)構(gòu)模式,并且還需要放棄會(huì)話連接過(guò)程中的一些控制,但是它確實(shí)抽離了很多底層的工作,減輕了工作量。

用 GameKit 開(kāi)發(fā)游戲可以同時(shí)滿足點(diǎn)對(duì)點(diǎn)模式和傳統(tǒng)網(wǎng)絡(luò)連接模式的需求。

Websockets

WebSocket 協(xié)議 (RFC 6455) 允許服務(wù)器端和客戶端之間進(jìn)行雙向通信。每個(gè)節(jié)點(diǎn)需要建立一個(gè)新的 websocket 連接。該協(xié)議建立在 TCP 的基礎(chǔ)上,所以不提供類似 MPC 的 .Unreliable 信息發(fā)送模式。不像 MPC,websocket 不提供任何網(wǎng)絡(luò)創(chuàng)建或者設(shè)備檢測(cè)功能,所以服務(wù)器端和客戶端都必須連接在同一個(gè)網(wǎng)絡(luò)上。它常用于和 Bonjour 關(guān)聯(lián)使用。

如果是構(gòu)建跨平臺(tái)游戲或應(yīng)用,那么 WebSocket 可以說(shuō)是極具吸引力的,不過(guò)它需要一個(gè)有自定義后臺(tái)的連接。

目前Swift (starscream) 和 Objective-C (SocketRocket、jetfire) 都有不少現(xiàn)成的 WebSocket 類庫(kù)可供使用。

總結(jié)

把 MPC 整合到你的游戲或者應(yīng)用中的過(guò)程不會(huì)很復(fù)雜,但是卻能極大的提升用戶體驗(yàn),希望讀完本文你也認(rèn)同此觀點(diǎn)。

如果想了解關(guān)于 MPC 的更多內(nèi)容,下面的資料可能會(huì)有所幫助。

資料

Multipeer Connectivity Reference

Multipeer Connectivity WWDC 2013 Session

GameKit Reference

NSHipster Article on Multipeer Connectivity

PeerKit: An open-source Swift framework for building event-driven, zero-config MPC apps

CardsAgainst: An open-source iOS game built with MPC

DeckRocket: An open-source presentation remote control app for iOS/OSX built with MPC

上一篇:UI 測(cè)試下一篇:音頻 API 一覽