この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
自己紹介
初めまして、来年度からクラスメソッドで勤務予定の荒川 靖久と申します。
現在はインターンでiOS関係のお仕事をさせて頂いております。ブログ記事もiOSの話題中心になるかと思います。
以前からゲームアプリを作っていたこともあり、ゲーム関連の記事も書けたら良いなと考えています。特にcocos2d frameworkが好きでcocos2d-xの勉強会に最近は出没したりします。
先日【iOS勉強会】iBeaconでできること – Developers.IO Meetup 02の最後のLT枠でiBeaconとcocos2dを絡めたネタをお話しさせて頂きました。
iBeaconは現在多くのiPhoneアプリ開発者から注目を浴びている技術の一つではありますが、まだまだ提案や試作段階の物が多いです。試してみたはいいけれども、よくわからなくてすぐ諦めたという方もいるかもしれません。
そこで数多くいるフロントエンジニア・ゲームエンジニアの方にiBeaconにもっと興味を持ってもらいたいということで今回はLTで紹介したアプリを一から作成する手順を紹介します。
開発環境構築(Macのみ)
- Xcodeのインストール
- Provisioningファイルに登録されたテストデバイス(実機2台)を用意
- cocos2d for iPhoneのインストール
Xcodeのインストールはこちらから行って下さい。
実機の用意は省略します。
cocos2d for iPhoneは公式サイトのDownloadのタブからOlder versions Stable versionの箇所にあるcocos2d-iphone-2.1.tar.gzを任意のディレクトリにダウンロードし、ダブルクリックして解凍します。解凍したディレクトリをFinderで見てみます。
そしてターミナル.appを起動(見つからなければmac画面右上の虫眼鏡アイコンを押し、出て来た検索ウィンドウに「ターミナル」と入力しEnter)し、
「cd(半角スペース)」と打ちます。
その後Finderで表示されているディレクトリをターミナルへドラッグ&ドロップします。
cd のあとに~/(ダウンロードしたファイルの場所)/cocos2d-iphoneと表示されたらEnterを押します。
その後「./install-templates.sh」と入力してEnterを押し、インストール完了(ok)のメッセージが表示されれば正常にインストールができています。
これでXcodeのプロジェクトからcocos2d for iPhoneのテンプレートが選択出来るようになります。
プロジェクトを作成する(Hello World)
Xcodeを起動し、Welcome to Xcodeと表示されたら、その下にある「Create a new Xcode project」を選択して下さい。
ここで正常にcocos2dのインストールが行えていればiOSの欄に「cocos2d v2.x」の項目が追加されているはずです。
選択したら一番左の「cocos2d iOS」を選択しNextを押します。以降は通常のプロジェクト作成と同じなので省略します。
プロジェクトが作成できたらまずXcodeメニュー画面左上のRunボタンを押すとこのように表示されます。
これでcocos2dの基本的な導入はおしまいです。
次にcocos2dのプロジェクトにCoreLocation.frameworkを導入します。
これでiBeaconを実装するための準備が整いました。
バックグランドでも処理を続けたい場合はGeneralの隣のタブ、Capabillitiesのバックグラウンドモードをオンにして、Location updatesにチェックを入れます。
今回は標準で用意されているHelloWorldLayer.h(Hello Worldが表示されているシーン)にCoreLocation.frameworkを追加していきます。
#import <GameKit/GameKit.h>
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
#import <CoreLocation/CoreLocation.h>
// HelloWorldLayer
@interface HelloWorldLayer : CCLayer
<GKAchievementViewControllerDelegate,
GKLeaderboardViewControllerDelegate,
CLLocationManagerDelegate>
{
}
@property (nonatomic, strong) CLLocationManager *locationManager_;
@property (nonatomic, strong) NSUUID *proximityUUID_;
@property (nonatomic, strong) CLBeaconRegion *beaconRegion_;
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
@end
- #importを追加
- CLLocationManagerDelegateを追加
- propertyを3つ追加
を行いました。
各プロパティの説明については[iOS 7] 新たな領域観測サービス iBeacon を使ってみるに詳細が書かれていますので、気になる方は参考にして下さい。
次にHelloWorldLayer.mに処理を記述していきます。
// Import the interfaces
#import "HelloWorldLayer.h"
// Needed to obtain the Navigation Controller
#import "AppDelegate.h"
#pragma mark - HelloWorldLayer
// ターミナルから $ uuidgen
static NSString * const kBeaconUUID = @"AA9D2576-7392-4763-AB84-6C30F8A8D366";
// 任意の識別子
static NSString * const kBeaconIdentifier = @"com.classmethod.testCocosBeacon";
// 建物などの識別子(16ビットまでの任意の自然数)
static NSUInteger const kTargetBeaconMajor = 1;
// 区画などの識別子(16ビットまでの任意の自然数)
static NSUInteger const kTargetBeaconMinor = 1;
// HelloWorldLayer implementation
@implementation HelloWorldLayer
// Helper class method that creates a Scene with the HelloWorldLayer as the only child.
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
// ask director for the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
label.position = ccp( size.width /2 , size.height/2 );
// add the label as a child to this Layer
[self addChild: label];
//
// Leaderboards and Achievements
//
// Default font size will be 28 points.
[CCMenuItemFont setFontSize:28];
// to avoid a retain-cycle with the menuitem and blocks
__block id copy_self = self;
// Achievement Menu Item using blocks
CCMenuItem *itemAchievement = [CCMenuItemFont itemWithString:@"Achievements" block:^(id sender) {
GKAchievementViewController *achivementViewController = [[GKAchievementViewController alloc] init];
achivementViewController.achievementDelegate = copy_self;
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] presentModalViewController:achivementViewController animated:YES];
[achivementViewController release];
}];
// Leaderboard Menu Item using blocks
CCMenuItem *itemLeaderboard = [CCMenuItemFont itemWithString:@"Leaderboard" block:^(id sender) {
GKLeaderboardViewController *leaderboardViewController = [[GKLeaderboardViewController alloc] init];
leaderboardViewController.leaderboardDelegate = copy_self;
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] presentModalViewController:leaderboardViewController animated:YES];
[leaderboardViewController release];
}];
CCMenu *menu = [CCMenu menuWithItems:itemAchievement, itemLeaderboard, nil];
[menu alignItemsHorizontallyWithPadding:20];
[menu setPosition:ccp( size.width/2, size.height/2 - 50)];
// Add the menu to the layer
[self addChild:menu];
[self startMonitoring];
}
return self;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#pragma mark GameKit delegate
-(void) achievementViewControllerDidFinish:(GKAchievementViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
-(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
[[app navController] dismissModalViewControllerAnimated:YES];
}
#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
{
CLBeacon *nearestBeacon = beacons.firstObject;
CCLOG(@"%@", [NSString stringWithFormat:@"major:%@\n minor:%@\n accuracy:%f\n rssi:%d",
nearestBeacon.major, nearestBeacon.minor, nearestBeacon.accuracy, nearestBeacon.rssi]);
}
- (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];
}
}
#pragma mark - Private methods
// Beaconによる領域観測を開始
- (void)startMonitoring
{
if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) {
// CLLocationManagerの生成とデリゲートの設定
self.locationManager_ = [CLLocationManager new];
self.locationManager_.delegate = self;
// 生成したUUIDからNSUUIDを作成
self.proximityUUID_ = [[NSUUID alloc] initWithUUIDString:kBeaconUUID];
// CLBeaconRegionを作成
self.beaconRegion_ = [[CLBeaconRegion alloc] initWithProximityUUID:self.proximityUUID_
major:kTargetBeaconMajor
identifier:kBeaconIdentifier];
[self.locationManager_ startMonitoringForRegion:self.beaconRegion_];
}
}
// 指定ビーコンの測定・監視を止める
- (void)stopMonitoring:(CLBeaconRegion *)region
{
if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
[self.locationManager_ stopMonitoringForRegion:self.beaconRegion_];
[self.locationManager_ stopRangingBeaconsInRegion:region];
self.locationManager_.delegate = nil;
self.locationManager_ = nil;
self.proximityUUID_ = nil;
self.beaconRegion_ = nil;
}
}
@end
以下を順番に実装します。
実機でデバッグ
ここで実行する時の注意点を挙げます。
上記を満たして成功すると
こんな感じにログが表示されます。これでPeripheralデバイス(Beacon送信側)との距離をBluetooth Low Energyで測定し、ログを表示することができるようになりました。iBeaconの基本的な実装はこれでおしまいです。
更に発展させるにはiBeacon Advent Calender - iBeaconとcocos2dを連携してみる - iBeaconでゲームアプリを作ってみたに書きましたので、興味を持って頂いた方はご覧になってみて下さい。