本系列博客將系統的介紹一款藍牙對戰五子棋的開發思路與過程,其中的核心部分有兩個,一部分是藍牙通信中對戰雙方信息交互框架的設計與開發,一部分是五子棋遊戲中棋盤邏輯與勝負斷定的算法實現。本篇博客將介紹遊戲中藍牙通信類的設計思路git
在前篇的一篇博客中,咱們有詳細的介紹iOS中藍牙4.0技術的應用與系統框架CoorBluetooth.framework中提供的編程接口的用法。博客地址以下,若是讀者須要更詳細的瞭解iOS中藍牙技術的使用,能夠先閱讀這篇博客:github
iOS開發之藍牙通信:http://my.oschina.net/u/2340880/blog/548127。算法
在使用藍牙進行應用間通信交互時,必須有一方做爲中心設備,有一方做爲外圍設備。舉一個簡單的例子,經過手機藍牙能夠和刷卡設備、打印機等進行信息交互,這裏的刷卡設備、打印機就充當着外圍設備的角色,手機就充當着中心設備的角色。在中心設備與外圍設備間,外設負責向周圍廣播廣告告知其餘設備本身的存在,中心設備接收到外設廣播的廣告後能夠選擇目標設備進行鏈接,固然,外設的廣播的廣告中會攜帶一些身份信息供中心設備進行識別。一旦中心設備與外設創建鏈接,中心設備變可使用外設提供的服務,一個外設能夠提供多個服務,例如一款藍牙打印機外設可能會提供兩種服務,一種服務向中心設備發送約定信息,告知中心設備支持的打印格式,一種服務獲取中心設備的數據來進行打印服務。服務是中心設備與外設機型通信的功能標識,然而具體的通信媒介則是由服務中的特徵值來完成的,一個服務也能夠提供多個特徵值。能夠這樣理解,特徵值是兩設備進行藍牙通信的最小通信單元,是讀寫數據的載體。編程
上面簡單介紹了在藍牙通信中的一些基本流程與相關概念,應用於遊戲中略微有一些區別,首先咱們這款遊戲應該具有既能夠做爲中心設備也能夠做爲外設的能力,所以,咱們須要將中心設備的通信模式與外設的通信模式都集成與遊戲的通信框架中。遊戲的雙方要創建鏈接應該有以下幾個過程:框架
1.有一方創建遊戲,做爲房主。async
2.由一方做爲遊戲的加入者,掃描附近的遊戲。工具
3.外設提供的服務中應該至少有兩個特徵值,一個用於己方下子後通知對方設備,一個用於監聽對方設備的下子操做。atom
由上面分析可知,遊戲中的房主正是充當藍牙通信中的外設,它將廣播廣告告知周圍設備本身的存在。而遊戲中的加入者則是充當着藍牙通信中的中心設備,掃描到周圍的遊戲房間後進行鏈接加入,開始遊戲。spa
建立一個命名爲BlueToothTool的工具類,做爲遊戲的藍牙通信類,編寫其頭文件以下:.net
BlueToothTool.h
#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文件以下:
#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地址:https://github.com/ZYHshao/BlueGame。
專一技術,熱愛生活,交流技術,也作朋友。
——琿少 QQ羣:203317592