2011年9月9日金曜日

iPhone Cocos2D 背景をカメラに!





Cocos2D は、iPhoneのゲームアプリでよく使われています。
ゲームアプリを作るならこれを勉強しましょう!


Cocos2D iPhone






まず、これを使うとどんないいことがあるかというと、
・ページの繊維がわかりやすい
・アニメーション豊富でわかりやすく使いやすい
・OpenGLESを普通に使うなら、絶対Cocos2Dを使ったほうが簡単
・フレームアニメーションが気持ちよくできる
・インターフェースビルダーが嫌いな人にはもってこい。インターフェースビルダーは一切使いません。




などなど、他にもたくさんありますが、とりあえず、ゲームっぽい動きのあるアプリを作るならば、使ったほうがいいです。




まずCocos2Dのインストールは、Cocos2dのインストールをどうぞ。










cocos2dで新規プロジェクトを作成すると、下記のようなファイルがあります。
※バージョンは、1.0.1です。




・GameConfig.h
・AppDelegate.h
・AppDelegate.m
・RootViewController.h
・RootViewController.m
・HellowWorldLayer.h
・HellowWorldLayer.m


※あとライブラリがごっそりあります。




通常のiPhoneアプリでは、Viewという概念で、ViewControllerがViewを表示して、NavigationControllerがViewControllerたちを遷移させてページを切り替えたりなど、Viewという概念の元動いています。


この方の記事がわかりやすいと思います。
UIViewとUIVIewControllerの違いについて




しかし、Cocos2Dではノードやシーン、レイヤーという概念になります。
cocos2dの基本的な概念




では、もともとのWindowや、UIViewControllerからどのような仕組みでcocos2Dの概念になっているのでしょうか。




こんな感じです↓↓↓




下から順番に


WIndow → RootViewController → EAGLView




Ditectorというのが、Cocos2Dでベースになる部分ですが、普通にやるならば、上記のことは一切気にしなくても問題ありません。


Ditectorというのに、シーンやレイヤーが入れ替わって画面遷移したり、レイヤーを重ねたりなどというような形になります。


HellowWorldLayerからNextLayerに切り替わるときは、例えばこんな感じです。




HellowWorldLayerにてボタンを押された際などに、










    id scene = [NextLayer scene];
    id transition = [CCTransitionFade transitionWithDuration:1.0f scene:scene];
    
    CCDirector *director = [CCDirector sharedDirector];
    
    [director replaceScene: transition];












これは、Fadeアニメーションをしつつ画面遷移するという内容です。
Ditectorに載っていたHellowWorldLayerからNextLayerに切り替えます。




このような形で、簡単に遷移できます。










大体、仕組みはこのようになりますが、もし、ARアプリのようなカメラを背景にしたい場合、ちょっと厄介です。




そもそも基本的に、UIView配下でしか使用できないものだからです。
他にもActionSheetや、UITextFiledなどのもともと使用できるものを使いたくても、Cocos2Dだと、ちょっと一工夫しなければ使用することができません。




その方法は、


EAGLView Ditector の下にあるRootViewControllerに貼ることです。


使いたいLayerで、




#import AppDelegate.h





※例えば、UITextField *textField = [UITextField alloc]initWith………];





AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.viewController addSubView: textField];






という形で、AppDelegateクラスの中で、RootViewControllerを@property 宣言しておけば、上記のようにappDelegateを取得し、RootViewControllerをレイヤーから取得できます。






ですが、これらは上記で概要を説明した通り、DitectorやLayerに関係なく、もとに引いてあるRootViewControllerに貼るわけですから、シーンやレイヤーを移動しても、関係なく表示され続けますので、そのもとのViewControllerから削除剃る必要があります。




[textField remove removeFromSuperView];


で、削除できます。






では、本題のカメラ背景です!






これも上記とやはり概念は同じなので、上記と同じように、RootViewControllerへ貼ることになります。




が、Cocos2Dのlayerの背景をカメラにするわけですから、かなり厄介です。
つまりは、こうなります。




Window → RootViewController ここにカメラを載せる → EAGLView → Layerなど




このような形にするためには、まずは、上記のUITextFieldと同じように、RootViewControllerへカメラを貼ります。
※通常はRootViewに貼るのですが、カメラの場合WIndowに対してはらないと、表示さないようです。なので、Windowの上に乗っかっているRootViewControllerの背景も透明にする必要があります。


詳細はこちら


ですが貼っただけでは、背景はカメラになりません。
なぜかというと、RootViewController(ここではWIndow)の上には、EAGLViewが乗っているからです。


なので、Cocos2Dアニメーションの描画元である、EAGLViewの背景を透明にする必要があります。




<手順>
1. 背景をカメラにしたいシーンやレイヤーで、RootViewController(Windowです)にカメラを貼る
2. EAGLViewの背景を透明にする








カメラを表示するには、UIImagePickerControllerを使うの一般的ですが、重たいという噂だったので、今回は、AVCaptureSessionを使用しました。




1. 背景カメラを挿入したいLayerで


// EAGLView の色を透明宣言しとく。(これだけでは透明にならないです)


glClearColor(0,0,0,0);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// captureSession 作成
captureSession = [[AVCaptureSession alloc] init];
if ([captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) {
    captureSession.sessionPreset = AVCaptureSessionPresetMedium;
}
            
NSError *error = nil;
            
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
 if (!input) {
    NSLog(@"input error");
 }
 [captureSession addInput:input];

// 実際に貼るvideoLayer作成
videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
            
CALayer *viewLayer = [[CCDirector sharedDirector] openGLView].layer;
//NSLog(@"viewLayer = %@", viewLayer);
            
videoPreviewLayer.frame = viewLayer.bounds;

// RootViewControllerへアクセス
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

// windowに対してカメラの場合、なぜかWindowじゃないと貼れません。
[appDelegate.window.layer insertSublayer:videoPreviewLayer below:appDelegate.viewController.view.layer];

// カメラをWIndowにはらなければならいので、
//その上に乗っかているRootViewControllerの背景も透明にする。
appDelegate.viewController.background = [UIColor clearColor];

[captureSession startRunning];



これで、Windowの上にカメラ映像を貼り、RootViewControllerの背景を透明にすることができました。



次にEAGLViewの背景を透明にします。


2. AppDelega.m

// 下記でEAGLViewのピクセルフォーマットをRGB565からRGBA8へ変更
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
                                 pixelFormat:kEAGLColorFormatRGB565


                                 pixelFormat:kEAGLColorFormatRGBA8    // kEAGLColorFormatRGBA8に変更
                                 depthFormat:0
];



EAGLViewのレイヤーに対して、同じピクセルフォーマット数でプロパティの設定をする
    
CAEAGLLayer *layer = (CAEAGLLayer*)glView.layer;
layer.opaque = NO;
    
layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithBool:YES], 
                                kEAGLDrawablePropertyRetainedBacking,
                                kEAGLColorFormatRGBA8, // kEAGLColorFormatRGBA8
                                kEAGLDrawablePropertyColorFormat, nil];



※ピクセルフォーマットをkEAGLColorFormatRGBA8にしないと、透明になりません。
しかし、kEAGLColorFormatRGBA8にすると、全体的な動作が重たくなりますのでご注意を。

これで実行すれば、背景がカメラになります。




以上です。

皆さんも是非試してみてください。

また内容がおかしかったり、もっといい方法がお分かりの方は、コメントください。













2 件のコメント:

  1. いい記事です。ありがとう。

    返信削除
  2. ありがとうございます!
    読んでいただいてうれしいです

    返信削除