有趣的低功耗蓝牙

lokikiller 9年前

来自: http://www.cocoachina.com/ios/20160218/15307.html

本文由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才能测试和上手体验。简单打开游戏,一个玩家选择服务端,另外一个玩家选择客户端模式,且保持你们的手机彼此紧挨着。连接成功时会有声音通知提醒。