本文共 12697 字,大约阅读时间需要 42 分钟。
本系列博客将系统的介绍一款蓝牙对战五子棋的开发思路与过程,其中的核心部分有两个,一部分是蓝牙通讯中对战双方信息交互框架的设计与开发,一部分是五子棋游戏中棋盘逻辑与胜负判定的算法实现。本篇博客将介绍游戏中蓝牙通讯类的设计思路
在前篇的一篇博客中,我们有详细的介绍iOS中蓝牙4.0技术的应用与系统框架CoorBluetooth.framework中提供的编程接口的用法。博客地址如下,如果读者需要更详细的了解iOS中蓝牙技术的使用,可以先阅读这篇博客:
iOS开发之蓝牙通讯:。
在使用蓝牙进行应用间通讯交互时,必须有一方作为中心设备,有一方作为外围设备。举一个简单的例子,通过手机蓝牙可以和刷卡设备、打印机等进行信息交互,这里的刷卡设备、打印机就充当着外围设备的角色,手机就充当着中心设备的角色。在中心设备与外围设备间,外设负责向周围广播广告告知其他设备自己的存在,中心设备接收到外设广播的广告后可以选择目标设备进行连接,当然,外设的广播的广告中会携带一些身份信息供中心设备进行识别。一旦中心设备与外设建立连接,中心设备变可以使用外设提供的服务,一个外设可以提供多个服务,例如一款蓝牙打印机外设可能会提供两种服务,一种服务向中心设备发送约定信息,告知中心设备支持的打印格式,一种服务获取中心设备的数据来进行打印服务。服务是中心设备与外设机型通讯的功能标识,然而具体的通讯媒介则是由服务中的特征值来完成的,一个服务也可以提供多个特征值。可以这样理解,特征值是两设备进行蓝牙通讯的最小通讯单元,是读写数据的载体。
上面简单介绍了在蓝牙通讯中的一些基本流程与相关概念,应用于游戏中略微有一些区别,首先我们这款游戏应该具备既可以作为中心设备也可以作为外设的能力,因此,我们需要将中心设备的通讯模式与外设的通讯模式都集成与游戏的通讯框架中。游戏的双方要建立连接应该有如下几个过程:
1.有一方建立游戏,作为房主。
2.由一方作为游戏的加入者,扫描附近的游戏。
3.外设提供的服务中应该至少有两个特征值,一个用于己方下子后通知对方设备,一个用于监听对方设备的下子操作。
由上面分析可知,游戏中的房主正是充当蓝牙通讯中的外设,它将广播广告告知周围设备自己的存在。而游戏中的加入者则是充当着蓝牙通讯中的中心设备,扫描到周围的游戏房间后进行连接加入,开始游戏。
创建一个命名为BlueToothTool的工具类,作为游戏的蓝牙通讯类,编写其头文件如下:
BlueToothTool.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import <CoreBluetooth/CoreBluetooth.h> //这个代理用于处理接收到对方设备发送来的数据后的回调 @protocol BlueToothToolDelegate <NSObject> //获取对方数据 -( void )getData:(NSString *)data; @end @interface BlueToothTool : NSObject<CBPeripheralManagerDelegate,CBCentralManagerDelegate,CBPeripheralDelegate,UIAlertViewDelegate> //代理 @property(nonatomic,weak)id<BlueToothToolDelegate>delegate; //标记是否是游戏中的房主 @property(nonatomic,assign) BOOL isCentral; /** *获取单例对象的方法 */ +(instancetype)sharedManager; /* *作为游戏的房主建立游戏房间 */ -( void )setUpGame:(NSString *)name block:( void (^)( BOOL first))finish; /* *作为游戏的加入者查找附近的游戏 */ -( void )searchGame; /** *断块连接 */ -( void )disConnect; /* *进行写数据操作 */ -( void )writeData:(NSString *)data; @end |
实现BlueToothTool.m文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | #import "BlueToothTool.h" @implementation BlueToothTool { //外设管理中心 CBPeripheralManager * _peripheralManager; //外设提供的服务 CBMutableService * _ser; //服务提供的读特征值 CBMutableCharacteristic * _readChara; //服务提供的写特征值 CBMutableCharacteristic * _writeChara; //等待对方加入的提示视图 UIView * _waitOtherView; //正在扫描附近游戏的提示视图 UIView * _searchGameView; //设备中心管理对象 CBCentralManager * _centerManger; //要连接的外设 CBPeripheral * _peripheral; //要交互的外设属性 CBCharacteristic * _centerReadChara; CBCharacteristic * _centerWriteChara; //开始游戏后的回调 告知先手与后手信息 void (^block)( BOOL first); } //实现单例方法 +(instancetype)sharedManager{ static BlueToothTool *tool = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ tool = [[self alloc] init]; }); return tool; } //实现创建游戏的方法 -( void )setUpGame:(NSString *)name block:( void (^)( BOOL ))finish{ block = [finish copy]; if (_peripheralManager==nil) { //初始化服务 _ser= [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00067" ] primary:YES]; //初始化特征 _readChara = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00067" ] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable]; _writeChara = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00068" ] properties:CBCharacteristicPropertyWriteWithoutResponse value:nil permissions:CBAttributePermissionsWriteable]; //向服务中添加特征 _ser.characteristics = @[_readChara,_writeChara]; _peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)]; } //设置为房主 _isCentral=YES; //开始广播广告 [_peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey:@ "WUZIGame" }]; } //外设检测蓝牙状态 -( void )peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{ //判断是否可用 if (peripheral.state==CBPeripheralManagerStatePoweredOn) { //添加服务 [_peripheralManager addService:_ser]; //开始广播广告 [_peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey:@ "WUZIGame" }]; } else { //弹提示框 dispatch_async(dispatch_get_main_queue(), ^{ [self showAlert]; }); } } //开始放广告的回调 -( void )peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{ if (_waitOtherView==nil) { _waitOtherView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; dispatch_async(dispatch_get_main_queue(), ^{ UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @ "等待附近玩家加入" ; [_waitOtherView addSubview:label]; _waitOtherView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [[[UIApplication sharedApplication].delegate window]addSubview:_waitOtherView]; }); } else { dispatch_async(dispatch_get_main_queue(), ^{ [_waitOtherView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_waitOtherView]; }); } } //添加服务后回调的方法 -( void )peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{ if (error) { NSLog(@ "添加服务失败" ); } NSLog(@ "添加服务成功" ); } //中心设备订阅特征值时回调 -( void )peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{ [_peripheralManager stopAdvertising]; if (_isCentral) { UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@ "" message:@ "请选择先手后手" delegate:self cancelButtonTitle:@ "我先手" otherButtonTitles:@ "我后手" , nil]; dispatch_async(dispatch_get_main_queue(), ^{ [_waitOtherView removeFromSuperview]; [alert show]; }); } } //收到写消息后的回调 -( void )peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{ dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate getData:[[NSString alloc]initWithData:requests.firstObject.value encoding:NSUTF8StringEncoding]]; }); } //弹提示框的方法 -( void )showAlert{ UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@ "温馨提示" message:@ "请确保您的蓝牙可用" delegate:nil cancelButtonTitle:@ "好的" otherButtonTitles:nil, nil]; [alert show]; } //===============================================================作为游戏加入这实现的方法======== //搜索周围游戏 -( void )searchGame{ if (_centerManger==nil) { _centerManger = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)]; } else { [_centerManger scanForPeripheralsWithServices:nil options:nil]; if (_searchGameView==nil) { _searchGameView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @ "正在扫加入描附近游戏" ; _searchGameView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [_searchGameView addSubview:label]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; } else { [_searchGameView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; } } //设置为游戏加入方 _isCentral = NO; } //设备硬件检测状态回调的方法 可用后开始扫描设备 -( void )centralManagerDidUpdateState:(CBCentralManager *)central{ if (_centerManger.state==CBCentralManagerStatePoweredOn) { [_centerManger scanForPeripheralsWithServices:nil options:nil]; if (_searchGameView==nil) { dispatch_async(dispatch_get_main_queue(), ^{ _searchGameView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @ "正在扫加入描附近游戏" ; _searchGameView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [_searchGameView addSubview:label]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; }); } else { dispatch_async(dispatch_get_main_queue(), ^{ [_searchGameView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; }); } } else { dispatch_async(dispatch_get_main_queue(), ^{ [self showAlert]; }); } } //发现外设后调用的方法 -( void )centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{ //获取设备的名称 或者广告中的相应字段来配对 NSString * name = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey]; if ([name isEqualToString:@ "WUZIGame" ]) { //保存此设备 _peripheral = peripheral; //进行连接 [_centerManger connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES}]; } } //连接外设成功的回调 -( void )centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@ "连接成功" ); //设置代理与搜索外设中的服务 [peripheral setDelegate:self]; [peripheral discoverServices:nil]; dispatch_async(dispatch_get_main_queue(), ^{ [_searchGameView removeFromSuperview]; }); } //连接断开 -( void )centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@ "连接断开" ); [_centerManger connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES}]; } //发现服务后回调的方法 -( void )peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ for (CBService *service in peripheral.services) { //发现服务 比较服务的UUID if ([service.UUID isEqual:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00067" ]]) { NSLog(@ "Service found with UUID: %@" , service.UUID); //查找服务中的特征值 [peripheral discoverCharacteristics:nil forService:service]; break ; } } } //开发服务中的特征值后回调的方法 -( void )peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ for (CBCharacteristic *characteristic in service.characteristics) { //发现特征 比较特征值得UUID 来获取所需要的 if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00067" ]]) { //保存特征值 _centerReadChara = characteristic; //监听特征值 [_peripheral setNotifyValue:YES forCharacteristic:_centerReadChara]; } if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@ "68753A44-4D6F-1226-9C60-0050E4C00068" ]]) { //保存特征值 _centerWriteChara = characteristic; } } } //所监听的特征值更新时回调的方法 - ( void )peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { //更新接收到的数据 NSLog(@ "%@" ,[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]); //要在主线程中刷新 dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate getData:[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]]; }); } -( void )alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ //告诉开发者先后手信息 if (buttonIndex==0) { if (_isCentral) { block(1); } else { block(0); } } else { if (_isCentral) { block(0); } else { block(1); } } } //断开连接 -( void )disConnect{ if (!_isCentral) { [_centerManger cancelPeripheralConnection:_peripheral]; [_peripheral setNotifyValue:NO forCharacteristic:_centerReadChara]; } } //写数据 -( void )writeData:(NSString *)data{ if (_isCentral) { [_peripheralManager updateValue:[data dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_readChara onSubscribedCentrals:nil]; } else { [_peripheral writeValue:[data dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_centerWriteChara type:CBCharacteristicWriteWithoutResponse]; } } @end |
附录:游戏的源码已经放在git上,时间比较仓促,只用了一下午来写,其中还有许多细节与bug没有进行调整,有需要的可以作为参考:
git地址:。
转载地址:http://wnqwa.baihongyu.com/