Skip to content

Commit b8cf090

Browse files
committed
Merge pull request #27 from nshintio/source_BluetoothHint
Hint - Bluetooth low energy fun way
2 parents ec1e76a + b6a5919 commit b8cf090

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
---
2+
layout: post
3+
title: "Bluetooth low energy the fun way"
4+
date: 2015-10-08 18:17:28 +0200
5+
comments: false
6+
author: marcin
7+
categories:
8+
---
9+
10+
Today Bluetooth Low Energy can be found in many cool applications, it can be used from simple data exchange to payment terminals and the more popular usage with iBeacons. But what if we want to build something funny with it? Like some simple game not even realtime, it may be even turn based game. Imagine you do not need to go through this long setup, waiting for server players to be ready etc.
11+
12+
Everyone knows that building good multiplayer game is hard, multiplayer itself is hard... But here I want to show you my small proof of concept of working bluetooth low enery multiplayer game.
13+
14+
It can be used in any kind of game! Strategy, board, rpg, race. I built§ a small demo project to show this in details but now let's focus on basics:
15+
16+
Pros:
17+
18+
1. It's simple!
19+
2. Works with any device
20+
3. No need to pair, login etc. Just come near other phone
21+
22+
Cons:
23+
24+
1. Bandwith (approx 30bytes of data per packet which todays is nothing)
25+
2. Limited distance (work well in approx 20m range)
26+
27+
We have our interface class that will be used to extend functionality on both server and client logic (we use central and peripheral mode of our phone)
28+
29+
```Swift
30+
31+
enum KWSPacketType : Int8 {
32+
33+
case HearBeat
34+
case Connect
35+
case Disconnect
36+
case MoveUp
37+
case MoveDown
38+
case Jump
39+
case Attack
40+
case DefenseUp
41+
case DefenseDown
42+
case Restart
43+
case GameEnd
44+
}
45+
46+
protocol KWSBlueToothLEDelegate: class {
47+
48+
func interfaceDidUpdate(interface interface: KWSBluetoothLEInterface, command: KWSPacketType, data: NSData?)
49+
}
50+
51+
class KWSBluetoothLEInterface: NSObject {
52+
53+
weak var delegate : KWSBlueToothLEDelegate?
54+
weak var ownerViewController : UIViewController?
55+
56+
var interfaceConnected : Bool = false
57+
58+
init(ownerController : UIViewController, delegate: KWSBlueToothLEDelegate) {
59+
60+
self.ownerViewController = ownerController
61+
self.delegate = delegate
62+
super.init()
63+
}
64+
65+
func sendCommand(command command: KWSPacketType, data: NSData?) {
66+
67+
self.doesNotRecognizeSelector(Selector(__FUNCTION__))
68+
}
69+
}
70+
71+
72+
```
73+
74+
As you can see, it's very simple - one send and one receive method as delegate. As both recive and send arguments, we can get the command used in your game to recognize packet type and data which will come along with this command.
75+
76+
Now we need to implement our server and client logic, i don't want to describe in details how to setup BluetoothLE on iPhone so insted I will highlight only important methods like receiving and sending packet on both client and server side.
77+
78+
`KWSBluetoothLEClient`
79+
80+
```Swift
81+
class KWSBluetoothLEClient: KWSBluetoothLEInterface, CBPeripheralManagerDelegate {
82+
83+
override func sendCommand(command command: KWSPacketType, data: NSData?) {
84+
85+
if !interfaceConnected {
86+
87+
return
88+
}
89+
90+
var header : Int8 = command.rawValue
91+
let dataToSend : NSMutableData = NSMutableData(bytes: &header, length: sizeof(Int8))
92+
93+
if let data = data {
94+
95+
dataToSend.appendData(data)
96+
}
97+
98+
if dataToSend.length > kKWSMaxPacketSize {
99+
100+
print("Error data packet to long!")
101+
102+
return
103+
}
104+
105+
self.peripheralManager.updateValue( dataToSend,
106+
forCharacteristic: self.readCharacteristic,
107+
onSubscribedCentrals: nil)
108+
109+
}
110+
111+
func peripheralManager(peripheral: CBPeripheralManager, didReceiveWriteRequests requests: [CBATTRequest]) {
112+
113+
if requests.count == 0 {
114+
115+
return;
116+
}
117+
118+
for req in requests as [CBATTRequest] {
119+
120+
let data : NSData = req.value!
121+
let header : NSData = data.subdataWithRange(NSMakeRange(0, sizeof(Int8)))
122+
123+
let remainingVal = data.length - sizeof(Int8)
124+
125+
var body : NSData? = nil
126+
127+
if remainingVal > 0 {
128+
129+
body = data.subdataWithRange(NSMakeRange(sizeof(Int8), remainingVal))
130+
}
131+
132+
let actionValue : UnsafePointer<Int8> = UnsafePointer<Int8>(header.bytes)
133+
let action : KWSPacketType = KWSPacketType(rawValue: actionValue.memory)!
134+
135+
self.delegate?.interfaceDidUpdate(interface: self, command: action, data: body)
136+
137+
self.peripheralManager.respondToRequest(req, withResult: CBATTError.Success)
138+
}
139+
}
140+
}
141+
```
142+
143+
`KWSBluetoothLEServer`
144+
145+
```Swift
146+
class KWSBluetoothLEServer: KWSBluetoothLEInterface, CBCentralManagerDelegate, CBPeripheralDelegate {
147+
148+
override func sendCommand(command command: KWSPacketType, data: NSData?){
149+
150+
if !interfaceConnected {
151+
152+
return
153+
}
154+
155+
var header : Int8 = command.rawValue
156+
let dataToSend : NSMutableData = NSMutableData(bytes: &header, length: sizeof(Int8))
157+
158+
if let data = data {
159+
160+
dataToSend.appendData(data)
161+
}
162+
163+
if dataToSend.length > kKWSMaxPacketSize {
164+
165+
print("Error data packet to long!")
166+
167+
return
168+
}
169+
170+
if let discoveredPeripheral = self.discoveredPeripheral {
171+
172+
discoveredPeripheral.writeValue( dataToSend,
173+
forCharacteristic: self.writeCharacteristic,
174+
type: .WithResponse)
175+
}
176+
}
177+
178+
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
179+
180+
if let error = error {
181+
print("didUpdateValueForCharacteristic error: \(error.localizedDescription)")
182+
return
183+
}
184+
185+
let data : NSData = characteristic.value!
186+
let header : NSData = data.subdataWithRange(NSMakeRange(0, sizeof(Int8)))
187+
188+
let remainingVal = data.length - sizeof(Int8)
189+
var body : NSData? = nil
190+
191+
if remainingVal > 0 {
192+
193+
body = data.subdataWithRange(NSMakeRange(sizeof(Int8), remainingVal))
194+
}
195+
196+
let actionValue : UnsafePointer<Int8> = UnsafePointer<Int8>(header.bytes)
197+
let action : KWSPacketType = KWSPacketType(rawValue: actionValue.memory)!
198+
199+
self.delegate?.interfaceDidUpdate(interface: self, command: action, data: body)
200+
}
201+
}
202+
```
203+
204+
In both cases sending and reciving is the same:
205+
206+
Sending:
207+
208+
1. Take raw value of the command
209+
2. Save into NSData
210+
3. Append using additional data that comes with the command
211+
4. Send to peripheral/central
212+
213+
Receive:
214+
215+
1. Take NSData from central / peripheral (update request status if needed)
216+
2. Get first byte to recognize command type
217+
3. Take subset of Data by removing 1st byte and store it as value coming along with command
218+
4. Take value of header byte and cast it to our PacketType
219+
5. Send it to delegate
220+
221+
Thanks to that we can build our game logic like this:
222+
223+
Setup:
224+
225+
```Swift
226+
227+
func setupGameLogic(becomeServer:Bool) {
228+
229+
self.isServer = becomeServer
230+
231+
if self.isServer {
232+
233+
self.communicationInterface = KWSBluetoothLEServer(ownerController: self, delegate: self)
234+
}
235+
else {
236+
237+
self.communicationInterface = KWSBluetoothLEClient(ownerController: self, delegate: self)
238+
}
239+
240+
}
241+
```
242+
243+
Sending data to other player:
244+
245+
```Swift
246+
247+
//player is dead notify other player
248+
self.communicationInterface!.sendCommand(command: .GameEnd, data: nil)
249+
250+
//send some basic data about your player state (life, position)
251+
252+
let currentPlayer = self.gameScene.selectedPlayer
253+
254+
var packet = syncPacket()
255+
packet.healt = currentPlayer!.healt
256+
packet.posx = Float16CompressorCompress(Float32(currentPlayer!.position.x))
257+
258+
let packetData = NSData(bytes: &packet, length: sizeof(syncPacket))
259+
self.communicationInterface!.sendCommand(command: .HearBeat, data: packetData)
260+
261+
//send some other info
262+
263+
let directionData = NSData(bytes: &currentPlayer!.movingLeft, length: sizeof(Bool))
264+
self.communicationInterface!.sendCommand(command: .MoveDown, data: directionData)
265+
266+
```
267+
268+
Reciving data:
269+
270+
```Swift
271+
func interfaceDidUpdate(interface interface: KWSBluetoothLEInterface, command: KWSPacketType, data: NSData?)
272+
{
273+
274+
switch( command ) {
275+
276+
case .HearBeat:
277+
if let data = data {
278+
279+
let subData : NSData = data.subdataWithRange(NSMakeRange(0, sizeof(syncPacket)))
280+
let packetMemory = UnsafePointer<syncPacket>(subData.bytes)
281+
let packet = packetMemory.memory
282+
283+
self.gameScene.otherPlayer!.healt = packet.healt
284+
self.gameScene.otherPlayer!.applyDamage(0)
285+
286+
let decoded = Float16CompressorDecompress(packet.posx)
287+
let realPos = self.gameScene.otherPlayer!.position
288+
let position = CGPointMake(CGFloat(decoded), CGFloat(realPos.y))
289+
290+
self.gameScene.otherPlayer!.position = position
291+
}
292+
293+
case .Jump:
294+
self.gameScene.otherPlayer!.playerJump()
295+
296+
case .Restart:
297+
self.unlockControls()
298+
299+
case .GameEnd:
300+
self.lockControls()
301+
302+
}
303+
}
304+
```
305+
306+
And i get some pretty promising results:
307+
308+
{% img center /images/bluetooth_low_energy/multi_btle.gif %}
309+
310+
Game works smoothly, there are no lags in connection and you can play almost instantly! And of course it allows you to integrate mutliplayer in your game in few minutes.
311+
312+
If you are starting your journey with gamedev or iOS and plan to build simple SpriteKit game with some basic multiplayer support it may be worth considering this option.
313+
314+
Demo project used to present the mechanics is available as always on [github](https://github.com/noxytrux/KnightWhoSaidSwift)
315+
316+
Game require at least two iPhone 5 to test and play. To start simply open game, one of the players choose server, other one client mode and bring your phone next to each another. Once you do that you should be notified about successfull connection by tone sound.
317+
Loading

0 commit comments

Comments
 (0)