cocos2dでの画面遷移(またはiPhoneで、コードでスクリーンショットをとる方法)

cocos2dを使ったゲーム開発で、紙芝居のように次画面をバックにおきつつ、現画面をスライドアウト(SlideOut)させることができないかと思い、試してみました。

cocos2dで画面遷移を行うには、replaceScene、pushScene、popSceneを利用します。その遷移の際には通常、CCTransitionファミリーのクラスを利用し、


[CCDirector sharedDirector] replaceScene:
	 [CCTransitionFlipX transitionWithDuration:0.8 scene:[MainMenuScene node]]];

のように記述することで、FadeoutやSlide In/outの効果を得ることができます。 多くの利用シーンではそれで問題ないのですが、次画面が後ろに存在していながら、現在の画面を紙芝居のようにスライドさせて遷移させるには、少し工夫が必要でした。状況に応じて2つの方法がとれたので、その方法をメモしておこうと思います。

popSceneをTransition対応にする

replaceSceneやpushSceneでは、上記のTransitionを利用することができますが、popSceneでは、現在のcocos2dのバージョン(1.0.1)では利用ができません。ただ、cocos2dのフォーラムでは議論がなされており、将来的には正式リリースにくみこまれるかもしれません。それを待っている訳にもいかないので、議論のあった記事を参考にさせてもらい、popSceneをTransition対応にします。 まず、CCDirector.hに以下のメソッド定義を追加します。

- (void) popSceneWithTransition: (Class)c duration:(ccTime)t;

次に、その実装をCCDirector.mに行いましょう。

 

-(void) popSceneWithTransition: (Class)transitionClass duration:(ccTime)t;
{
	NSAssert( runningScene_ != nil, @"A running Scene is needed");
[scenesStack_ removeLastObject];
NSUInteger c = [scenesStack_ count];
if( c == 0 ) {
[self end];
} else {
CCScene* scene = [transitionClass transitionWithDuration:t scene:[scenesStack_ objectAtIndex:c-1]];
[scenesStack_ replaceObjectAtIndex:c-1 withObject:scene];
nextScene_ = scene;
}
}

これで、popSceneでもTransitionが利用できるようになりました。実際には以下のようにして使います。

 

[CCDirector sharedDirector] popSceneWithTransition:[CCTransitionFlipX class] duration:0.8];

比較的軽い修正により、popSceneでもトランジション効果が得られるため、非常にスマートな方法だと思います。しかし、この方法には1つの制限があります。popSceneはその名の通り、シーンをPOPするので、pushされているシーンに対してしか利用できないということです。 つまり、アプリの設定画面や入力補助画面など、一時的に利用する画面をpushし、入力が終わったらそれをpopする場合には多いに利用できるでしょうが、メインメニューから各機能へ遷移するボタンでの利用の時などは、そもそも、後ろ(次の)画面にどのシーンが来るかは前もってわかりません。次に遷移する可能性のあるシーンをすべてpushしておくのも、メモリに余裕の無いスマートフォンでは、あまり良い方法とは言えないでしょう。

Layerを利用して、画面遷移効果を得る

そこで、すこしコーディングして、同じような効果を得ることにしました。 処理の流れは以下のような感じです。 ①遷移ボタンが押されたタイミングで、現在のシーンのキャプチャをとる。 ②このキャプチャしたイメージを引数にして、次のシーンを生成し、キャプチャから作ったLayerを画面のレイヤー階層の一番上に配置する。 ③現画面から次画面に切り替える。(同じ見た目なので、ユーザには切り替わったように見えない) ④完全に次画面に切り替わった後で、キャプチャLayerを自由に動かして、画面遷移の効果とする。 ①で現在の画面のキャプチャをとるため、画面がどのような状態であるか気にしないで、次の画面を用意できることが、この方法のメリットだと思います。 さて、私が実装した内容を以下に記述したいと思います。 まず、現在のシーンのキャプチャをとります。シーンを生成するクラスのどこに書いても良いと思いますが、私は、AppDelegateに記述しました。キャプチャするコードは、フォーラムのこの記事を参考にしました。

- (UIImage *)takeAsUIImageInRect:(CGRect)rect
{
	CCDirector* director =

[CCDirector sharedDirector];
	if(rect.origin.x < 0)
		rect.origin = CGPointMake(0, rect.origin.y);
	if(rect.origin.y  [director winSize].width)
		rect.size = CGSizeMake([director winSize].width, rect.size.height);
	if(rect.size.height > [director winSize].height)
		rect.size = CGSizeMake(rect.size.width, [director winSize].height);
	if(rect.origin.x+rect.size.width > [director winSize].width)
		rect.size = CGSizeMake(floor([director winSize].width - rect.origin.x), rect.size.height);
	if(rect.origin.y+rect.size.height > [director winSize].height)
		rect.size = CGSizeMake(rect.size.width, floor([director winSize].height - rect.origin.y));

	CGSize size = rect.size;

	//Create buffer for pixels
	GLuint bufferLength = size.width * size.height * 4;
	GLubyte* buffer = (GLubyte*)malloc(bufferLength);

	//Read Pixels from OpenGL
	glReadPixels(rect.origin.x, rect.origin.y, size.width, size.height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
	//Make data provider with data.
	CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, bufferLength, NULL);

	//Configure image
	int bitsPerComponent = 8;
	int bitsPerPixel = 32;
	int bytesPerRow = 4 * size.width;
	CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
	CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
	CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
	CGImageRef iref = CGImageCreate(size.width, size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

	uint32_t* pixels = (uint32_t*)malloc(bufferLength);
	CGContextRef context = CGBitmapContextCreate(pixels, size.width, size.height, 8, size.width * 4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

	CGContextTranslateCTM(context, 0, size.height);
	CGContextScaleCTM(context, 1.0f, -1.0f);

	switch (director.deviceOrientation)
	{
		case CCDeviceOrientationPortrait:
			break;
		case CCDeviceOrientationPortraitUpsideDown:
			CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(180));
			CGContextTranslateCTM(context, -size.width, -size.height);
			break;
		case CCDeviceOrientationLandscapeLeft:
			CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(-90));
			CGContextTranslateCTM(context, -size.height, 0);
			break;
		case CCDeviceOrientationLandscapeRight:
			CGContextRotateCTM(context, CC_DEGREES_TO_RADIANS(90));
			CGContextTranslateCTM(context, size.width * 0.5f, -size.height);
			break;
	}

	CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, size.width, size.height), iref);
	UIImage *outputImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];

	//Dealloc
	CGDataProviderRelease(provider);
	CGImageRelease(iref);
	CGContextRelease(context);
	free(buffer);
	free(pixels);

	return outputImage;
}

次に、いろいろな場面で使えるように、キャプチャのレイヤー用のクラスを作成しておきます。各シーンは、このキャプチャした画像を引数に生成し、クラス内部に保持しておきます。

CaptureLayer.h

@interface CaptureLayer : CCLayer {  }

+(id) sceneWithScreenShot:(UIImage *)screenImage;
-(id) initWithScreenShot:(UIImage *)screenImage;

@end

CaptureLayer.m

 

@implementation CaptureLayer
+(id) sceneWithScreenShot:(UIImage *)screenImage {

CCScene *scene =

[CCScene node];

	CaptureLayer *layer = [[self alloc] initWithScreenShot:screenImage];

	[scene addChild: layer];

	return scene;
}

-(id) init
{
	if( (self=[super init])) {

	}
	return self;
}

- (id) initWithScreenShot:(UIImage *)screenImage {

	if ((self = [super init])){

		CCTexture2D *tex = [[CCTexture2D alloc] initWithImage: screenImage];
		CCSprite *background = [[CCSprite spriteWithTexture:tex] retain];
		[background setPosition:ccp(160,206)];
		[self addChild:background];
		[background release];
	}

	return self;
}

- (void) dealloc
{
	[super dealloc];
}

@end

最後に、このCaptureLayerを保持したシーンが完全に生成されたタイミングで、Layerが動き始めるよう、Sceneクラスの中で、以下の実装を加えます。

Scene.m

-(void) onEnterTransitionDidFinish {

	[super onEnterTransitionDidFinish];

	[captureLayer runAction:[CCMoveTo actionWithDuration:0.5 position:ccp(0,-1000)]];

}

以上で、紙芝居のようなFlideOut効果を得ることができました。実機で動かしてみましたが、うまく動いてくれているようです。

コラム

cocos2dでゲームを作る際に参考にした本です。初心者用、中級者用とありますので、参考にしてください。私には非常に役立ちましたよ!

cocos2dで作る iPhone&iPadゲームプログラミング cocos2dで作る iPhone&iPadゲームプログラミング

これは非常にいい本です。画面遷移の基本から応用まで、体系だって解説がなされており、ゲーム作成時に何度も読み返しました。パフォーマンスを考慮したコーディングや物理エンジンについても言及があります。

  cocos2d for iPhoneレッスンノート cocos2d for iPhoneレッスンノート

cocos2dを使ってゲームを作るのが初めての人に最適です。いくつかのサンプルのプロジェクトとその解説があり、それをもとに、簡単なゲームは作れるようになるでしょう。初心者がつまづきやすい、AppStoreへの申請方法も例示されています。初めてゲームをリリースするときに参考にしました。

 

Learning Cocos2D: A Hands-On Guide to Building iOS Games with Cocos2D, Box2D, and Chipmunk Learning Cocos2D: A Hands-On Guide to Building iOS Games with Cocos2D, Box2D, and Chipmunk

cocos2dで物理エンジンを利用したゲームを作る際に参考になります。英語ですが、物理エンジンを解説した本は少ないため、貴重です。

 



こんな記事も関係あるかも。読んでみてね。

コメントを残す

サブコンテンツ

このページの先頭へ