cocos2d Advent Calendarに参加しました。
2011/12/21の担当です。
■概要
UIKitを使って簡単にcocos2d画面にUIを配置して使ってみる。
しかもXIBで、レイアウトも簡単だったら良いよね、ということでやってみた。
cocos2dからUIKitを使うと、座標云々で面倒である。
なので、普通のiOSからcocos2dを使う方法を取ってみた。
■だが、まずはcocos2d_box2dプロジェクト作成
そろそろHelloWorldではつまらないので、box2dでプロジェクトを作成してみる。
タイトル画面に使う適当な背景画像(haikei.jpg)を用意し、横画面対応にする。
次に、禁断のmain.mの変更を行う。(※main.mは、普通は変更する事は無い)
これは初期状態ではcocos2d制御になるが、それをXIB制御にするためである。
main.m 変更
int retVal = UIApplicationMain(argc, argv, nil, nil);
■普通のiOS仕様に仕立て上げる
あくまでも普通のiOSぽく、XIBを作ってそこから各画面に遷移する構造にする。(※現時点でStoryBoardの方法は不明)
まずMainWindow.xibを「Window」で作成し、ObjectLibraryから、Objectを追加する。
Objectのclassは、AppDelegateを設定。
File's Ownerのclassは、UIAppricationに設定。
File's OwnerのConnectionsにDelegateが出てくるので、AppDelegateと結ぶ。
そして、AppDelegate.hのwindowのプロパティにIBOutletを付けてやる。
AppDelegate.h 追記
@property (nonatomic, retain) IBOutlet UIWindow *window;
AppDelegateにwindowのConnectionが表示されるので、Windowと結びつけ、NavigationControllerを追加する。
WindowのRootViewControllerを、NavigationControllerに結びつける。
NavigationControllerのclassにTitleを設定し、ついでにNavigationItemのTitleもTitleに変更する。
ひとまず、MainWindow.xibを上記の用に設計し、次に各画面の構築を行う。
■各画面作成とタイトル画面表示
Title.xibを「View」で作成、Title.mと、Title.hをObjective-C classで作成する。SubclassはUIViewControllerのままで良い。
今回は横画面になるので、Title.xibのAttributesのOrientationを、PortraitからLandscapeに変更する。
タイトル画面ぽく画像を配置し、InfoViewController.xibへのボタンと、RootViewController.xibへのボタンを配置する。
右下の「i」ボタンは、AttributesのTypeをInfo Lightに変更すると形が変わる。
classは同名のTitleにすると、Connectionsにviewが出るので、Viewと結びつける。
InfoViewController.xib、InfoViewController.m、InfoViewController.h、RootViewController.xibも同様に作成しておく。
※RootViewController.mは既に有るものを使うので作成の必要は無いが、C++でコンパイルする必要が有るため、RootViewController.mmにリネームする。
Title.hを下記のように記載し、各画面へのボタン定義と、classを読み込んでおく。
Title.h
#import <UIKit/UIKit.h>
#import "RootViewController.h"
#import "InfoViewController.h"
@interface Title : UIViewController {
RootViewController *_rootViewController;
InfoViewController *_infoViewController;
}
@property (retain) InfoViewController *infoViewController;
@property (retain) RootViewController *rootViewController;
- (IBAction)infoTapped:(id)sender;
- (IBAction)viewTapped:(id)sender;
@end
InfoViewController.xibは下記のように作成してみた。
Title.xibでも行ったが、File's Ownerのclass設定と、Viewとの結びつけを忘れずに。
RootViewController.xibは下記のように背景を黒にし、Titleボタンだけ作成し、FullScreenに変更した。
Status BarもNoneにしておく。
■各画面への遷移組み立て
さて、ここまでやったが、まだ動くアプリケーションは出来ていない。
各画面に遷移するプログラムを行う必要がある。
まずはタイトル画面だけ出したい。
MainWindow.xibをMain Interfaceに設定する。
MainWindow.xibのViewControllerのNIB NameをTitleにし、NavigationControllerのAttributesのPresentation>Defines Contextをオフにする。
AppDelegate.mの一部を下記のようにコメントアウトする。
AppDelegate.m コメントアウト
/*
#import "HelloWorldLayer.h"
#import "RootViewController.h"
*/
// window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
/*
// Init the View Controller
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
viewController.wantsFullScreenLayout = YES;
//
// Create the EAGLView manually
// 1. Create a RGB565 format. Alternative: RGBA8
// 2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition
//
//
EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
// attach the openglView to the director
[director setOpenGLView:glView];
*/
/*
// make the OpenGLView a child of the view controller
[viewController setView:glView];
// make the View Controller a child of the main window
[window addSubview: viewController.view];
*/
// [[CCDirector sharedDirector] runWithScene: [HelloWorldLayer sceneWithLastCalendar:0]];
Title.mを、とりあえず下記のように設定しておく。(※後程、更に更新する)
Title.m 暫定コード
@implementation Title
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationIsLandscape(interfaceOrientation);
}
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
@end
ここまでで、ボタンは機能しないが、タイトル画面は出るはずだ。
■InfoViewController画面への遷移
ここまで出来たら、各画面への遷移をプログラミングする。
Title.hについてはここまでの経緯で完成しているので、Title.mに各ボタンの遷移プログラムを記載する。
Title.m
#import "Title.h"
@implementation Title
@synthesize rootViewController = _rootViewController;
@synthesize infoViewController = _infoViewController;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationIsLandscape(interfaceOrientation);
}
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
- (IBAction)infoTapped:(id)sender {
if (_infoViewController == nil) {
self.infoViewController = [[[InfoViewController alloc] initWithNibName:nil bundle:nil] autorelease];
}
[self.navigationController pushViewController:_infoViewController animated:YES];
}
- (void)viewWallpapers:(id)arg {
if (_rootViewController == nil) {
self.rootViewController = [[[RootViewController alloc] initWithNibName:nil bundle:nil] autorelease];
}
[self.navigationController pushViewController:_rootViewController animated:YES];
}
- (IBAction)viewTapped:(id)sender {
[self viewWallpapers:nil];
}
- (void)dealloc
{
[_rootViewController release];
_rootViewController = nil;
[super dealloc];
}
@end
これで、ボタンとの紐付けが可能になるので、InfoViewController、RootViewControllerへのIBActionとボタンのTouch Up Insideを結びつける。
なんとなく、タイトルラベルも貼ってみた。
まずは、InfoViewControllerへの遷移をプログラムし、確認してみよう。
ボタンの紐付けは終わっているが、受け側にもプログラムを記載する必要がある。
InfoViewController.h
#import <UIKit/UIKit.h>
@interface InfoViewController : UIViewController {
InfoViewController *_infoViewController;
}
@property (retain) InfoViewController *infoViewController;
@end
InfoViewController.m
#import "InfoViewController.h"
@implementation InfoViewController
@synthesize infoViewController = _infoViewController;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:NO animated:animated];
[super viewWillAppear:animated];
}
- (void)dealloc
{
[_infoViewController release];
_infoViewController = nil;
[super dealloc];
}
@end
InfoViewController.xibの作成は、ここまでで完了しているはずなので、タイトル画面右下の「i」ボタンを押してみよう。
下記のように、ナビゲージョンバーに「Title」ボタンが出て、戻る事が出来れば成功である。
■RootViewController画面への遷移
基本的にはInfoViewControllerと似ている。
遷移ボタンは出来ているはずなので、タイトル画面中央の「Start cocos2d」ボタンを押せば黒い画面にTitleボタンだけ出る。
まずは、Titleボタンの実装と、ViewDidUnLoadだけ実装し、タイトル画面とInfoViewController画面の行き来を確認しよう。
RootViewController.m 追記
- (IBAction)homeTapped:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
-(void)ViewDidUnLoad の最後に追記
[[CCDirector sharedDirector] end];
とりあえず、下の画面が出て、Titleボタンでタイトル画面に戻れればOK。
さて、まだcocos2dが登場していない。
次はcocos2dのbox2dをRootViewControllerにハメ込む事にする。
■cocos2dをロードし、画面を重ねる
当記事の最初でcocos2dを切り離しているので、ここからセットアップしてやる必要がある。
RootViewController.mmの先頭のほうに下記を追記する。
Viewが呼ばれたときに、setupCocos2Dを実行しセットアップした後、HelloWorldに振っている。
RootViewController.mm 先頭に追記
#import "cocos2d.h"
#import "HelloWorldLayer.h" //追記
#import "RootViewController.h"
#import "GameConfig.h"
@implementation RootViewController
- (void)setupCocos2D {
EAGLView *glView = [EAGLView viewWithFrame:self.view.bounds
pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8
depthFormat:0 // GL_DEPTH_COMPONENT16_OES
];
glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:glView atIndex:0];
[[CCDirector sharedDirector] setOpenGLView:glView];
CCScene *scene = [HelloWorldLayer node];
[[CCDirector sharedDirector] runWithScene:scene];
}
- (void) viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:NO];
[super viewWillAppear:animated];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupCocos2D];
}
これで、RootViewControllerの下でHelloWorld(box2d)が実行され、タイトル画面にも戻る事が出来る。
■セグメントコントロールを取り入れる
ここまでは、単に普通のiOSからcocos2dを起動しただけだが、UIKitからcocos2dに変化をもたらせてやることにする。
RootViewController.xibに、Segmented Controlを追加し、箱の種類を選ばせてみる。
ButtonChangedメソッドを作り、Segmented ControlのValue Changedと結ぶ。
RootViewController にメソッド追加
//RootViewController.h に追記
- (IBAction)buttonChanged:(id)sender;
//RootViewController.mm に追記
- (IBAction)buttonChanged:(id)sender {
NSUserDefaults* def = [NSUserDefaults standardUserDefaults];
[def setInteger:[sender selectedSegmentIndex] forKey:@"boxType"];
}
HelloWorldLayer.mmも変更する必要がある。
初期値は「ランダム」ということで、セグメントでいう4をinitで設定しておくのと、
セグメントで選ばれた箱を指定する処理を入れる。
HelloWorldLayer.mm のinitに追加
NSUserDefaults* def = [NSUserDefaults standardUserDefaults];
[def setInteger:4 forKey:@"boxType"];
HelloWorldLayer.mm 変更
//変更前 int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);
//変更後
int idx, idy;
NSUserDefaults* def = [NSUserDefaults standardUserDefaults];
NSInteger boxType = [def integerForKey:@"boxType"];
switch( boxType ) {
case 0:
idx = 0;
idy = 0;
break;
case 1:
idx = 1;
idy = 0;
break;
case 2:
idx = 0;
idy = 1;
break;
case 3:
idx = 1;
idy = 1;
break;
case 4:
default:
idx = (CCRANDOM_0_1() > .5 ? 0:1);
idy = (CCRANDOM_0_1() > .5 ? 0:1);
break;
}
これで、セグメントで選んだ箱が追加出来るようになる。
■ポイント
最初が少々面倒ではあるが、これで後々cocos2dとUIKitの組み合わせがかなり楽になる。
ただし、2つの画面を重ねているため、重い処理には向かないと思われる。
■その他
RootViewController.mmの、setupCocos2Dの中で下記の記述をすると半透明で重ねる事も出来る。
alphaの値は0.0〜1.0。
添削アドバイス:@ajinotataki氏