iBeaconアプリをこれから作ってみたい人へ
はじめに、iBeaconは提供されて間もない技術でもあり、既存のGPSや位置情報を扱えるCoreLocation(CoreBluetoothではない方)ほどの豊富な機能はありません。
さらにCoreLocation同様、検知エリアから出た際のExit判定が30秒ほど待機しなければ検知されない(検知エリア出入りの誤検知を防ぐための間だと思います。)など、意図とは違うタイミングで呼び出される事があります。
しかし、UIさえ工夫すれば現状でも作れるアプリはたくさんあります。
仮にiBeaconの新規アプリケーションを作成・提案する際に以下のポイントを抑えておけば、アプリ作成時のハードルが下がると思いますのでまとめます。
この記事を読むと
- Beaconとの距離目安(4段階の距離)によってイベントが起こるアプリ
- Beaconとの距離(およそ何m)でイベントが起こるアプリ
- 移動しながら遊べる、ゲームアプリ
を作るための開発のヒントが得られます。
※新規の方に作ってみたいと思って頂けるようにプロパティの名称などは噛み砕いた日本語で書きます。
※Xcodeでのプロジェクトの作成やフレームワークのインポートは既にできている想定で書きます。
この記事を読んで、作れるアプリをイメージしていただけるようになると幸いです。
実装時UIを動的に動かす、もしくはアプリとしての面白みを持たせられるポイント
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
これが検知エリアに入り、かつレンジング(測定)を開始した時におよそ1秒毎に呼び出されるメソッドです。
つまり
Objective-Cでの
[NSTimer timerWithTimeInterval:1.0f target:(id) selector:(SEL) userInfo:(id) repeats:YES];
や
cocos2dでの
[self schedule:(SEL) interval:1.0f];
のタイマー処理とほぼ同等の処理が行えます。
すなわちここに何かしらの処理(座標移動や拡大縮小、色や透明度の変化)を記述しておくことでアニメーションの様に表示することができます。
ゲームアプリを作る際には、上記のような定期処理をすることが多いです。
Beaconの観測開始をゲームのスタートと捉えることによって簡単にゲームアプリのスタートを行うことができます。
更に都合の良い事に、ゲームロジックや自然なアニメーションに必要な変動値が引数の beacons に入っていますので利用しない手はありません。そのためにまずはBeaconを取得しています。
個々のBeaconを取得する
引数の(NSArray *)beaconsには測定中のBeaconが距離が近い順に格納されています。ただし測定距離は電波強度で計測しているため正確に近い順にソートされているかは保証できません。
このbeaconsを
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
for (CLBeacon *beacon in beacons) {
// 計測中のBeaconがbeaconに順番に渡される
}
}
などで順に取り出すことによって、それぞれのBeaconからのプロパティ(Beaconとの距離目安、電波強度、組織識別子、区画識別子、距離(m単位))を取り出す事が出来ます。
ちなみに最も近いBeacon1台だけの処理をしたい時は
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
// 配列の先頭を取得(すなわち最寄りのBeaconを取得)
CLBeacon *beacon = beacons.firstObject;
}
これで簡単に取得できます。
特定の位置に接近した時だけイベントを起こしたい場合(例えばデバイスをBeaconにかざすと何か処理をしたい時など)はこの取り方で十分です。
次は取得したBeaconから距離を取得してみます。
Beaconからの距離を取得する
上記で取得したbeaconから、プロパティ(Beaconとの距離目安、電波強度、組織識別子、区画識別子、距離(m単位))を取り出します。
このプロパティについては他の方もたくさんまとめていらっしゃいますので割愛します。
距離の取得の方法は大まかに領域を測定する方法とメートル単位での取得方法の2つがあります。
この2つが「ゲームロジックや自然なアニメーションに必要な変動値」になります。
Beaconからの大まかな距離を取得する
上記で取得したbeaconの proximity の値で4段階の距離目安(非常に近い、近い、遠い、測定範囲外)が取れます。Beaconからの正確な距離は必要とせず、領域の移動でイベントを起こしたい場合などに最適です。
メリット:
- 距離で処理をした場合に比べて計測のブレに左右されずらい。
デメリット:
- 目安の境目のところを行ったり来たりした際に、正しく処理を行わなければならない。通知を出す場合は同じ通知を複数出さないなどの注意が必要。
実装するとこういうのができる:
- 計測エリア内に入った時にお知らせが出るアプリ。
- Beaconに端末をかざすことでポイントが入るアプリ。
- 直線上の区画(商店街など)を移動した際に次々と表示を変えられるアプリ。
※直線でないと難しいです。
Beaconからの距離(m単位)を取得する
上記で取得したbeaconの accuracy の値で電波強度から計測したBeaconとの距離が取れます。
Beaconから離れたり近づいたりした時の距離で画面表示を変えたい場合などに最適です。
メリット:
- 距離目安で処理をした場合に比べて毎秒細かい変化が付けられる。
デメリット:
- 計測が若干遅いので、端末の移動後数秒立ち止まらないと正しい距離は取れない。恐らく、直近の何回かの計測の平均で移動値や加速値を出しているのではないかと思われる。
実装するとこういうのができる:
- 宝探しアプリ。
- 2つ以上のiOS端末を使ったかくれんぼのアプリ。
- 領域内のBeaconまで直線距離何mかが表示ができるアプリ。
※計測までに時間がかかるのと、方角が取れないのでナビのように使用するのは難しい。
上記2つを組み合わせることによって「観測領域内の距離目安をまたいだ時と、距離が変わった時」にアプリ内でのイベントを追加することができます。
実際に作ってみる
まず、estimoteとかのBeaconを持ってないんだけどiBeaconのアプリって作れるの?
と思われる方もいらっしゃると思いますが、iOSもしくはMac OS X端末をBeaconの送信機として動かすこともできます。
長くなるので送信機側のコードは載せません。
以上をふまえて、今回は私の好きなcocos2d for iPhoneと連携して遊んでみます。
cocos2dと連携させる理由は
- イージングなどのアニメーションの実装が楽
- パーティクルが使える
- レンジング(測定)が1秒毎に処理されるのでゲームロジックの定期処理がそのまま使えそうだったから
です。
実装時のポイントは1点 「実装したいCCLayerでのCoreLocation frameworkの実装」 です。
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import <CoreLocation/CoreLocation.h>
@interface DetecroreScene : CCLayer <CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *locationManager_;
@property (nonatomic, strong) NSUUID *proximityUUID_;
@property (nonatomic, strong) CLBeaconRegion *beaconRegion_;
+(CCScene *) scene;
@end
#import "DetecroreScene.h"
#import "AppDelegate.h"
+ (CCScene *)scene {
CCScene *scene = [CCScene node];
DetecroreScene *detectoreScene = [DetecroreScene node];
[scene addChild:detectoreScene];
return scene;
}
- (id)init
{
self = [super init];
if (self) {
if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) {
// CLLocationManagerの生成とデリゲートの設定
self.locationManager_ = [CLLocationManager new];
self.locationManager_.delegate = self;
// 生成したUUIDからNSUUIDを作成
self.proximityUUID_ = [[NSUUID alloc] initWithUUIDString:@"UUID文字列"];
// CLBeaconRegionを作成
self.beaconRegion_ = [[CLBeaconRegion alloc] initWithProximityUUID:self.proximityUUID_
major:@"任意の整数値"
identifier:@"任意のID"];
[self.locationManager_ startMonitoringForRegion:self.beaconRegion_];
}
}
return self;
}
#pragma mark - CLLocationManagerDelegate methods
// 画面を表示した時に通知を受け取る
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
CCLOG(@"Determine State");
// 観測可能領域内かつ測定可能
if(state == CLRegionStateInside && [CLLocationManager isRangingAvailable]) {
[self.locationManager_ startRangingBeaconsInRegion:self.beaconRegion_];
}
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
CCLOG(@"Start Monitoring Region");
if([CLLocationManager isRangingAvailable]) {
[self.locationManager_ startRangingBeaconsInRegion:self.beaconRegion_];
}
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
CCLOG(@"Enter Region");
// 測定開始
if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
[self.locationManager_ startRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
CCLOG(@"Exit Region");
// 測定停止
if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
[self.locationManager_ stopRangingBeaconsInRegion:(CLBeaconRegion *)region];
}
}
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
/*
測定を始めると1秒毎に定期的に呼ばれる
ここにゲームのロジック(配置したオブジェクトの移動、拡大縮小、色・透明度の変化など)を書く
*/
}
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{
if(error.code == kCLErrorRegionMonitoringFailure) {
CCLOG(@"Fail for Region");
} else {
[[[UIAlertView alloc] initWithTitle:@"error"
message:error.description
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] show];
}
}
※上記のコードはCoreLocationを利用しているところのみを抜粋しています。実際のゲームロジックはGitHubに上げてありますのでそちらをご覧下さい。
コード解説
上記のコードはiBeaconのアプリを実装する時に必要な処理とほとんど同じです。
違うところはcocos2dのクラス「CCLayer」を継承していることとそれのコンビニエンスコンストラクタになる「+(CCScene *) scene;」を実装することです。
これでcocos2dのクラスであるCCLayerのサブクラスでのCoreLocation frameworkの実装が可能となりました。
まとめ
現状、iBeaconを実装しただけのアプリですと一般ユーザーには何のアプリかがほとんど伝わらないと思います。
Bluetoothを日常的にONにしてくれる人もあまりいないと思います。
また業務向けに作るとしても過大評価されている(近距離GPS遂に出た!NFCもういらないの?のような)こともあるようで、相手にどういったものかも伝えにくいと思います。
CoreLocation(GPS)を利用したアプリにはマップなどがあり、そちらはMKMapViewという非常に直感的に操作できる画面が用意されているので似たようなものと捉えられがちです。
そういうこともあってCoreBluetoothのiBeaconアプリを作るのであれば、他のフレームワークなどと積極的に絡めていくと良いと思います。
直感的に伝わる見せ方やデザインがやはり大事です。
iBeaconで2台のデバイスを使うゲーム作るぞー!って感じの勢いで私は作れましたので、少しでも開発者の敷居が下がると良いなと願っております。
iBeaconの処理は他にもPassbookとも連携がさせやすいようです。
発展
今回作ったアプリは某「ドラゴンレーダー」をイメージして作り始めたのですが、あそこまでうまくはできませんでした。
アプリ自体はBeaconとの距離目安・距離で処理を変え、minor値でBeaconが目当ての物か判定しているだけです。
応用するのであれば人探しなどの短距離コミュニケーションアプリにするか、リアル脱出ゲームのようなアミューズメント系の施設で使えたら面白いと思います。
むしろ現状測定できる精度の低さなどから、そういったことに使うためにあるんじゃないかと思っております。
送信機側のminor値をいじって、いくつものBeaconを建物内に配置し、お目当てのお宝(minorが一致するBeacon)を探すとかとか...。
実装してみたら色々遊べそうなのとサービスの幅が広がるなと思えたので、是非ここまで読んで下さった方は作ってみて下さい。
実際に作ってみると文章で見るより、本当にサービスの視野が広がると思います。