一、开场白:
1》首先给大家推荐下关于IOS蓝牙开发的相关资料博客链接(相当不错的文章)
1、IOS蓝牙开发_ios蓝牙4.0中心模块
2、iOS - Bluetooth 蓝牙
2》本篇博客Unity_IOS_蓝牙通信,主要核心实现功能就是通过以上两篇文章实现。有时间的朋友可以对其好好阅读。
3》本篇博客重心是Unity与IOS交互,并且实现调用IOS与蓝牙通信功能。
二、效果展示(本案例是Unity IOS 蓝牙通信,连接带有蓝牙钢琴设备)
1)图1:打开准备连接钢琴界面进行蓝牙初始化判断
2)图2:如果打开已经打开蓝牙,即可点击搜索周围蓝牙设备。
3)搜索到,点击连接需要的蓝牙设备。可以进行通信
注释:本案例是连接带有蓝牙功能的钢琴设备。连接上之后,点击钢琴键,就会发送信息,让对应的钢琴键亮。
三、开发流程
1》实现IOS与蓝牙通信,并且实现Unity与IOS交互的IOS层代码。
1.1)首先创建两个文件ViewController.h 和 ViewController.m。(可通过xcode或者记事本创建都行,文件名称可以不一样,关键在于后缀名。顾名思义一个头文件.h相当于接口类,一个.m文件相当于实现类)
1.2)首先我们在头文件ViewController.h里面定义需要导入的库,以及声明unity调用ios交互的方法。
//
// ViewController.h
// iosPlugins
// Created by os on 2020/4/10.
// Copyright © 2020 os. All rights reserved.
//
#import
//导入CoreBluetooth头文件(蓝牙的核心库)
#import
@interface ViewController :UIViewController
//以下是Unity调用IOS蓝牙接口方法声明
//1、初始化
void _Init();
//2、发现设备
void FindBlueDevice();
//3、连接设备 @deviceIdentifier:蓝牙设备
void ConnectDevice(char* deviceIdentifier);
//4、关闭扫描设备
void CancelFindDevice();
//5、断开设备@deviceIdentifier:蓝牙设备
void DisconnectDevice(char* deviceIdentifier);
@end
1.3)接下来要实现IOS与蓝牙通信的核心代码。以下代码都是在ViewController.m里实现
1.3.1:建立中心管理类
#import "ViewController.h"
@interface ViewController(){}
@end
//1、系统蓝牙设备管理对象,可以把他理解为主设备,通过他,可以去扫描和链接外设
CBCentralManager *manager;
//用于保存被发现设备
NSMutableDictionary *peripherals;
@implementation ViewController
//实例化ViewController类(方法前+号可以理解为静态,减号可以理解为非静态或者私有)
static ViewController* gameMgr = nil;
+(ViewController*)instance
{
if(gameMgr==nil)
{
gameMgr = [[ViewController alloc]init];
}
return gameMgr;
}
//2、初始化(建立中心管理类,设置代理)
-(id)init{
self = [super init];
if(self){
peripherals = [[NSMutableDictionary alloc] init];//初始化发现设备集合
//初始化并设置委托和线程队列,最好一个线程的参数可以为nil,默认会就main
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
//执行完中心管理类,会直接进入centralManagerDidUpdateState判断蓝牙开启状态
//[self centralManagerDidUpdateState:manager];
}
return self;
}
1.4)通过判断中心管理器蓝牙状态,如果开启可以进行扫描外设
//3/扫描外设
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBManagerStateUnknown:
NSLog(@">>>CBManager状态未知!");
OnBlueState("01");
break;
case CBCentralManagerStateResetting:
NSLog(@">>>CBCentralManager状态重置");
OnBlueState("02");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>>当前设备不支持蓝牙功能!");
OnBlueState("03");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>>当前设备未授权打开蓝牙");
OnBlueState("04");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>>蓝牙未打开,系统会自动提示打开,所以不用自行提示!");
OnBlueState("0");
break;
case CBCentralManagerStatePoweredOn:
NSLog(@">>>蓝牙已打开,开始扫描周围蓝牙设备!");
OnBlueState("1");
//到这里就可以进行扫描设备调用。这里我们先不去调用,因为要通过unity发送命令去扫描,我们接下来自定义一个扫描的方法。
break;
default:
break;
}
}
1.5)扫描外设(自定义一个方法)
//自定义方法,开始扫描外设
-(void)scanNearPerpherals{
NSLog(@"开始扫描周围设备!");
if(peripherals != nil){
[peripherals removeAllObjects];
}
[manager scanForPeripheralsWithServices:nil options:nil];
}
1.6)扫描外设,如果扫描到设备会直接进入下面方法。
//扫描到设备会进入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!
NSString* name = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
if(peripherals != nil && peripheral != nil && name != nil){
NSString *identifier = nil;
NSString *foundPeripheral = [self findPeripheralName:peripheral];
if(foundPeripheral == nil)
{
identifier = [[NSUUID UUID] UUIDString];
}
else
{
identifier = foundPeripheral;
}
//将扫描到的设备信息(设备名称和identifier用分号拼接起来)
NSString *message = [NSString stringWithFormat:@"%@;%@",name,identifier];
NSLog(@"message:%@",message);
if(message != nil)
{
//判断是否包含有,防止重复接收扫描的蓝牙设备
CBPeripheral * ISperipheral = [peripherals objectForKey:identifier];
if(ISperipheral == nil)
{
const char* devicesInfo = [message cStringUsingEncoding:NSASCIIStringEncoding];//NNString转换为char
GetBluetoothDevice(devicesInfo);//将扫描到的设备信息通知给unity
[peripherals setObject:peripheral forKey:identifier];//保存扫描到的设备
}
}
}
}
-(NSString *)findPeripheralName:(CBPeripheral*)peripheral
{
NSString *foundPeripheral = nil;
NSEnumerator *enumerator = [peripherals keyEnumerator];
id key;
while((key = [enumerator nextObject]))
{
CBPeripheral *tempPeripheral = [peripherals objectForKey:key];
if([tempPeripheral isEqual:peripheral])
{
foundPeripheral = key;
break;
}
}
return foundPeripheral;
}
1.7)连接指定的设备,自定义方法
//连接制定设备,自定义方法
-(void)connectToPeripheral:(NSString *)name{
if(peripherals != nil && name != nil)
{
CBPeripheral * peripheral = [peripherals objectForKey:name];
if(peripheral != nil)
{
[manager connectPeripheral:peripheral options:nil];
}
}
}
1.8)连接成功与否,会进入以下回调的方法。
//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>连接到名称为(%@)的设备-成功",peripheral.name);
//停止扫描
[self stopScanDevice:manager];
OnConnected("true");//通知Unity我连接成功了状态
//设置的peripheral委托CBPeripheralDelegate,设置代理
[peripheral setDelegate:self];
//扫描外设Services,成功后会进入方法-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
//连接到Peripherals-失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
OnConnected("false");
}
//Peripherals断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
OnConnected("false");
}
1.9)连接设备成功,接下来就是发现蓝牙设备的服务。即上面通过[peripheral discoverServices:nil];方法去调用。当然这里发现服务也是可以过滤指定要发现的服务,比如可以这样写 [peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]];这样的好处,有利于研发自己认定服务进行之后的通信操作。
//扫描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//NSLog(@">>>扫描到服务:%@",peripheral.services);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@">>>扫描到外设服务:%@",service.UUID);
//扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
2.0)扫描到服务后,即可扫描服务的所有特征Characteristics
//获取外设的Characteristics,获取Characterristics的值,获取Characteris的Descriptor和Descriptor的值
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"扫描到服务service:%@ 的 特征Characteristic: %@",service.UUID,characteristic.UUID);
//获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
//[peripheral readValueForCharacteristic:characteristic];
//这里我们通过订阅方式,实现实时接收蓝牙设备发送的信息
[self notifyCharacteristic:peripheral characteristic:characteristic];
//搜索特征的Descriptors(这里面没有用到,暂时屏蔽)
//搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
//[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
2.1)获取的特征charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据characteristic.value
if(characteristic.value != NULL)
{
NSData *data = characteristic.value;
if(data != NULL && data.length == 5){
//将接收到的十六进制数据转成十六进制字符串
Byte* resultByte = (Byte*)[data bytes];
//将需要到的数据发送给unity
NSString *result1 = [NSString stringWithFormat:@"%x",resultByte[2]&0xff];
NSString *result2 = [NSString stringWithFormat:@"%x",resultByte[3]&0xff];
const char* char1 = [result1 cStringUsingEncoding:NSASCIIStringEncoding];
const char* char2 = [result2 cStringUsingEncoding:NSASCIIStringEncoding];
const char* char3 = _LinkCharByFH(char1,char2);
OnDisPlayData(char3);
}
}
}
2.2)搜索到Characteristic的Descriptors,以及获取Descriptors的值。(可以根据自己需求使用)
//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"搜索到特征:%@ 的Descriptors :%@",characteristic.UUID,d.UUID);
//获取到Descriptor的值
[peripheral readValueForDescriptor:d];
}
}
//获取到Descriptors的值,协议方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
NSLog(@"获取到descriptor uuid:%@ 的值:%@ ",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
2.3)如果有写数据的需求,可以添加写数据到特征中
//写数据到特征中完成,协议方法
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:( CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"写数据完成到特征:%@ 中完成:%@",characteristic.UUID,characteristic.value);
}
//把数据写到Characteristic中
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
NSLog(@"%lu", (unsigned long)characteristic.properties);
//只有 characteristic.properties 有write的权限才可以写
if(characteristic.properties & CBCharacteristicPropertyWrite || characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
/*
最好一个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈
*/
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"该字段不可写!");
}
}
2.4)订阅Characteristic的通知一般在discoverCharacteristic的代理中,发现了类型是notify的characteristic,直接就可以订阅了。
//订阅Characteristic的通知,一般在discoverCharacteristic的代理中,发现了类型是notify的characteristic,直接就可以订阅了
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
NSLog(@"设置通知,数据通知会进入:didUpdateValueForCharacteristic方法");
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//如果订阅,成功与否的回调是peripheral:didiUpdateNotificationStateForCharacteristic:error,读取中的错误已error行驶传回
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:( CBCharacteristic *)characteristic error:(NSError *)error{
if(error)
{
NSLog(@"Error changing notification state:%@",[error localizedDescription]);
}
NSLog(@"订阅成功与否characteristic uuid:%@ value:%@",characteristic.UUID,[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]);
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
NSLog(@"取消通知");
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
2.5)停止扫描,以及断开连接
//停止扫描
-(void)stopScanDevice:(CBCentralManager *)centralManager
{
//停止扫描
if(centralManager != nil)
[centralManager stopScan];
}
//停止扫描并断开连接
-(void)disconnectPeripheral:(NSString *)name
{
if(peripherals != nil && name != nil)
{
CBPeripheral *peripheral = [peripherals objectForKey:name];
if(peripheral != nil)
{
if(manager != nil)
{
//停止扫描
[manager stopScan];
//断开连接
[manager cancelPeripheralConnection:peripheral];
}
}
}
}
2.6)到这里基本上就已经完成IOS与蓝牙通信的功能。接下来我们把Unity要调用IOS的接口实现。
//蓝牙外部调用部分-----------------start
//1、初始化蓝牙设备
void _Init(){
[ViewController instance];
}
//2、发现设备
void FindBlueDevice(){
if(gameMgr!=NULL)//判断蓝牙状态进行扫描
{
//[gameMgr centralManagerDidUpdateState:manager];
[gameMgr scanNearPerpherals];
}
else{//否则重新初始化,进行蓝牙扫描
[ViewController instance];
}
}
//3、连接设备 @deviceIdentifier:设备identifier
void ConnectDevice(char* deviceIdentifier){
if(gameMgr!=NULL && deviceIdentifier!=NULL)
{
[gameMgr connectToPeripheral:[NSString stringWithFormat:@"%s",deviceIdentifier]];
}
}
//4、关闭扫描设备
void CancelFindDevice(){
[gameMgr stopScanDevice:manager];
}
//5、断开设备连接
void DisconnectDevice(char* deviceIdentifier){
if(gameMgr!=NULL && deviceIdentifier!=NULL)
{
[gameMgr disconnectPeripheral:[NSString stringWithFormat:@"%s",deviceIdentifier]];
}
}
//6、IOS发送信息到Unity
//6.1、接收搜索到蓝牙设备信息--
void GetBluetoothDevice(const char* msg)
{
UnitySendMessage("BluetoothController","GetBluetoothDevice",MakeStringCopy(msg));
}
//6.2、蓝牙连接状态---传递到unity(flag:true表示连接上,flag:false;表示没有连接上)
void OnConnected(char* flag)
{
UnitySendMessage("BluetoothController","OnConnected",MakeStringCopy(flag));
}
//6.3、蓝牙传递信息过来信息---传递到unity
void OnDisPlayData(const char* msg)
{
UnitySendMessage("BluetoothController","OnDisPlayData",MakeStringCopy(msg));
}
//6.4、信息提示
void OnShowMessage(const char* msg)
{
UnitySendMessage("BluetoothController","OnShowMessage",MakeStringCopy(msg));
}
//6.5、蓝牙状态提示
void OnBlueState(const char* msg)
{
UnitySendMessage("BluetoothController","OnBlueState",MakeStringCopy(msg));
}
//蓝牙外部调用部分-----------------end
@end
//----------------------用到工具类---------start
//两个字符串相连接,中间用分号隔开
char* _LinkCharByFH(const char* str1,const char* str2)
{
char str[80];
char* flag = "";
strcpy(str, str1);
strcat(str, flag);
strcat(str, str2);
return MakeStringCopy(str);
}
//防止内存泄漏,崩溃,这里进行参数转换
char* MakeStringCopy(const char* string){
if(string == NULL){
return NULL;
}
char* res = (char*)malloc(strlen(string)+1);
strcpy(res, string);
return res;
}
//----------------------用到工具类---------end
到这里IOS与蓝牙通信,以及IOS要与Unity交互的接口,已经完成。
2》下面就是要实现Unity端界面的实现,以及Unity与IOS之间方法的传递交互功能。(首先把做好的上面IOS两个文件,放到Unity:Plugins/IOS/文件夹下面)
2.1)首先要创建一个C#脚本,然后挂载到到一个地方,根据自己需求挂载(比如挂载到相机上,或者Canvas上,也可以自定义名称。注意这个要与UnitySendMessage("BluetoothController","OnBlueState",MakeStringCopy(msg)); 里面设置的第一个参数保持一致)
///
/// 初始化蓝牙设备
///
[DllImport("__Internal")]
private static extern void _Init();
public void Init()
{
#if !UNITY_EDITOR
#if UNITY_IOS
_Init();
#endif
#endif
}
///
/// 接收ios传递过来,当前主设备蓝牙状态
///
public void OnBlueState(string msg)
{
if (msg == "1")
{
IsBlueOpen = true;
}
else
{
IsBlueOpen = false;
}
}
///
/// 发现设备,即搜索设备
///
[DllImport("__Internal")]
private static extern void FindBlueDevice();
public void findBlueDevice()
{
#if !UNITY_EDITOR
#if UNITY_IOS
FindBlueDevice();
#endif
#endif
}
///
/// 连接设备
///
/// 是设备的identifier,每个手机显示会不一样
[DllImport("__Internal")]
private static extern void ConnectDevice(string _macAddress);
public void connectDevice(string deviceName,string macAddress,GameObject gObj)
{
#if !UNITY_EDITOR
#if UNITY_IOS
ConnectDevice(macAddress);
#endif
#endif
}
///
/// 接收设备信息(ios--->unity)
///
///
public void GetBluetoothDevice(string deviceInfo)
{
try
{
if (deviceInfo != "" && deviceInfo.Contains(";"))
{
string deviceName = deviceInfo.Split(';')[0];
string deviceAddress = deviceInfo.Split(';')[1];
if (deviceList != null && deviceList.Contains(deviceAddress))
{
//包含则,不用再添加
}
else
{
if (deviceName == "" || deviceName == null || deviceName == "null")
{
deviceName = "N/A";
return;
}
//判断蓝牙名称是否包含在内(过滤蓝牙名称)
if (PublicUtils.FilterBLName(deviceName))
{
deviceList.Add(deviceAddress);
}
}
}
}
catch (Exception err) {
Debug.Log(err.Message);
}
}
///
/// 蓝牙连接状态反馈
///
public void OnConnected(string flag)
{
if (flag == "true")
{
showToast("蓝牙设备已连接!");
}
else
{
showToast("蓝牙设备已断开!");
}
}
///
/// 接收钢琴传递参数
///
///
public void OnDisPlayData(string msg)
{
if (msg != "")
{
msg = msg.Replace(" ", "");
showToast("接收传递过来的信息:"+ msg);
//根据需求进行处理
}
}
///
/// 关闭扫描设备
///
[DllImport("__Internal")]
private static extern void CancelFindDevice();
public void cancelFindDevice()
{
#if !UNITY_EDITOR
#if UNITY_IOS
CancelFindDevice();
#endif
#endif
}
///
/// 断开设备连接
///
[DllImport("__Internal")]
private static extern void DisconnectDevice(string _macAddress);
public void disconnectDevice(string macAddress)
{
#if !UNITY_EDITOR
#if UNITY_IOS
DisconnectDevice(macAddress);
#endif
#endif
}
到这里Unity端与ios之间的交互已经完成。
接下来就是根据自己的需求设计界面,以及调用相关的方法,进行设计。有好的想法欢迎一起交流沟通。
3》发布或者测试时候,需要注意以下几点,需要注意的几个地方。
3.1)需要添加蓝牙库。在TARGETS--->Unity-iPhone--->Build Phases--->Link Binary With Libraries--->添加"CoreBluetooth.framework".
3.2)添加打开蓝牙提示权限。在info.plist里面添加NSBluetoothAlwaysUsageDescription 或者 Privacy - Bluetooth Peripheral Usage Description 权限字段 值设置为:"app需要打开你的蓝牙?"
后续会给大家介绍:
1、Unity Android 蓝牙通信功能的实现。
2、Unity Android USB方式连接钢琴的实现。
3、Unity Windows USB方式连接电钢的实现
4、Unity IOS USB方式连接电钢的实现
如有问题可以微信咨询,可扫描下方微信添加互相沟通:
如果对你有帮助,请给点赞助支持下奥!