本文由CocoaChina譯者KingNotJustAName翻譯
原文:Bluetooth Low Energy the Fun Way
目前很多炫酷的應用中都使用了低功耗藍牙技術,它能夠用於簡單的數據交換、支付終端以及采用iBeacon技術的用途上。但如果我們想要創建一些有趣的事情呢?比如一些非實時的簡單游戲。想象下你無需經過長時間的設置等待服務器玩家做好准備等。
每個人都知道開發優秀的多人游戲是困難的,並且多人游戲本身就很難……不過這裡我展示下我在多人游戲中使用低功耗藍牙技術的一些小經驗。
它可以用於任何類型的游戲!比如策略游戲、棋盤游戲、角色扮演游戲以及競賽游戲等。我創建了一個小的demo示例來展示使用細節,但現在要關注基礎知識。
優點:
很簡單!
適用於任何設備。
不需要配對、登錄等。只需靠近其他手機。
缺點:
帶寬(一個數據包大概30字節)
距離限制(適用范圍大約在20米內)
我們將會把接口類用在基於服務器和客戶端邏輯的功能擴展上(我們電話使用中心和外圍設備模式)
正如你所看到的,很簡單,一個發送,一個接收方法作為委托,並且同時接收和發送參數,我們可以在游戲中使用指令,通過這個指令我們可以識別數據包類型和數據。
現在我們需要實現服務端和客戶端的邏輯,我不想具體描述怎麼在蘋果手機上設置藍牙,相反我只強調像客戶端和服務端接收和發送數據之類的方法。
KWSBluetoothLEClient
enum KWSPacketType : Int8 { case HearBeat case Connect case Disconnect case MoveUp case MoveDown case Jump case Attack case DefenseUp case DefenseDown case Restart case GameEnd } protocol KWSBlueToothLEDelegate: class { func interfaceDidUpdate(interface interface: KWSBluetoothLEInterface, command: KWSPacketType, data: NSData?) } class KWSBluetoothLEInterface: NSObject { weak var delegate : KWSBlueToothLEDelegate? weak var ownerViewController : UIViewController? var interfaceConnected : Bool = false init(ownerController : UIViewController, delegate: KWSBlueToothLEDelegate) { self.ownerViewController = ownerController self.delegate = delegate super.init() } func sendCommand(command command: KWSPacketType, data: NSData?) { self.doesNotRecognizeSelector(Selector(__FUNCTION__)) } }
KWSBluetoothLEServer
class KWSBluetoothLEClient: KWSBluetoothLEInterface, CBPeripheralManagerDelegate { override func sendCommand(command command: KWSPacketType, data: NSData?) { if !interfaceConnected { return } var header : Int8 = command.rawValue let dataToSend : NSMutableData = NSMutableData(bytes: &header, length: sizeof(Int8)) if let data = data { dataToSend.appendData(data) } if dataToSend.length > kKWSMaxPacketSize { print("Error data packet to long!") return } self.peripheralManager.updateValue( dataToSend, forCharacteristic: self.readCharacteristic, onSubscribedCentrals: nil) } func peripheralManager(peripheral: CBPeripheralManager, didReceiveWriteRequests requests: [CBATTRequest]) { if requests.count == 0 { return; } for req in requests as [CBATTRequest] { let data : NSData = req.value! let header : NSData = data.subdataWithRange(NSMakeRange(0, sizeof(Int8))) let remainingVal = data.length - sizeof(Int8) var body : NSData? = nil if remainingVal > 0 { body = data.subdataWithRange(NSMakeRange(sizeof(Int8), remainingVal)) } let actionValue : UnsafePointer = UnsafePointer(header.bytes) let action : KWSPacketType = KWSPacketType(rawValue: actionValue.memory)! self.delegate?.interfaceDidUpdate(interface: self, command: action, data: body) self.peripheralManager.respondToRequest(req, withResult: CBATTError.Success) } } }
在上述代碼中,發送數據和接收數據是相同的:
發送:
1、獲取原始命令值
2、保存到NSData
3、帶有額外附加指令的數據
4、發送給外圍設備/中心
接收:
1、從中心/外圍設備獲取NSData(如果需要更新請求狀態)
2、獲取第一個字節來識別命令類型
3、通過刪除第一個字節獲取子集且把它存儲為值和指令
4、獲取值的報頭字節且賦值給PacketType
5、把它發送給代理
設置:
func setupGameLogic(becomeServer:Bool) { self.isServer = becomeServer if self.isServer { self.communicationInterface = KWSBluetoothLEServer(ownerController: self, delegate: self) } else { self.communicationInterface = KWSBluetoothLEClient(ownerController: self, delegate: self) } }
將數據發送到其它成員:
//player is dead notify other player self.communicationInterface!.sendCommand(command: .GameEnd, data: nil) //send some basic data about your player state (life, position) let currentPlayer = self.gameScene.selectedPlayer var packet = syncPacket() packet.healt = currentPlayer!.healt packet.posx = Float16CompressorCompress(Float32(currentPlayer!.position.x)) let packetData = NSData(bytes: &packet, length: sizeof(syncPacket)) self.communicationInterface!.sendCommand(command: .HearBeat, data: packetData) //send some other info let directionData = NSData(bytes: ¤tPlayer!.movingLeft, length: sizeof(Bool)) self.communicationInterface!.sendCommand(command: .MoveDown, data: directionData)
接收數據:
func interfaceDidUpdate(interface interface: KWSBluetoothLEInterface, command: KWSPacketType, data: NSData?) { switch( command ) { case .HearBeat: if let data = data { let subData : NSData = data.subdataWithRange(NSMakeRange(0, sizeof(syncPacket))) let packetMemory = UnsafePointer(subData.bytes) let packet = packetMemory.memory self.gameScene.otherPlayer!.healt = packet.healt self.gameScene.otherPlayer!.applyDamage(0) let decoded = Float16CompressorDecompress(packet.posx) let realPos = self.gameScene.otherPlayer!.position let position = CGPointMake(CGFloat(decoded), CGFloat(realPos.y)) self.gameScene.otherPlayer!.position = position } case .Jump: self.gameScene.otherPlayer!.playerJump() case .Restart: self.unlockControls() case .GameEnd: self.lockControls() } }
我得到一些很有價值的結果:
游戲運行順利,不存在連接延遲,你可以立刻體驗了!當然它還允許你在游戲裡花幾分鐘創建多個玩家。
如果你即將開始游戲開發或iOS開發之旅,或計劃創建一些簡單的支持多人玩家的SpriteKit游戲,這個選擇可能值得考慮。
示例工程可在github上找到。
游戲至少需要兩個iPhone5才能測試和上手體驗。簡單打開游戲,一個玩家選擇服務端,另外一個玩家選擇客戶端模式,且保持你們的手機彼此緊挨著。連接成功時會有聲音通知提醒。
更多譯者翻譯文章,請查看:http://www.cocoachina.com/special/translation/
感謝博文視點對本期活動的支持