游戏必然涉及到场景的切换(蜘蛛人不在此列)。
如果新场景在init的时候需要加载大量的资源,在这段时间中用户界面将会失去响应。这对于玩家而言是一个非常糟糕的体验,他可能觉得你把他搞死机了 :)
我们在WOW中进入副本的瞬间其实就涉及到场景的切换,很明显的是暴风雪的程序员并没有让我们体验这种糟糕的感觉,他们会设计一个过场场景来告诉我们加载了百分之多少了。
记住:等待比不知所措要好得多。
先看展示动画:展示动画
本例涉及到3个场景,源场景,目标场景和过渡场景。由于动画中看不到鼠标点击的操作,所以你可能会觉得有点不知所措。听我来解释一下。
在源场景和目标场景的init方法中都有复杂的浮点数除法运算,这可能会需要3秒钟的时间(模拟器环境下,真机可能更长)。
当源场景被触摸以后,直接使用导演的replaceScene切换到目标场景,你会发现显示帧率的区域停止了跳动,游戏此时进入"失去响应状态"。
在目标场景中我们再次触摸屏幕,此时借助过渡场景来replaceScene到目标场景,loading界面出现,相同的时间后顺利切换到源场景。
来看实现过程,代码结构如下图:
源场景和目标场景其实都是用层来构造的,过程场景是一个CCSecen。
先看FirstLayer中的内容:
// FirstLayer.m
// CH05
//
// Created by phc on 11-12-12.
// Copyright 2011年 hxsoft. All rights reserved.
//
#import "FirstLayer.h"
#import "NextLayer.h"
@implementation FirstLayer
-(void)dealloc
{
[super dealloc];
}
+(id)scene
{
CCScene *sc = [CCScene node];
[sc addChild:[FirstLayer node]];
return sc;
}
//用浮点数除法折腾
-(void)zheteng
{
float a = 2423;
float b = 3432;
for (int i = 0; i < 300000000; i++) {
a /= b;
}
}
-(id)init
{
if (self = [super init]) {
CCLabelTTF *lbl = [CCLabelTTF labelWithString:@"点击屏幕切换场景(没有loading)" fontName:@"Arial" fontSize:18];
[self addChild:lbl];
CGSize sizeOfWin = [[CCDirector sharedDirector] winSize];
lbl.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfWin.height * 0.5);
[self zheteng];
[self setIsTouchEnabled:YES];
}
return self;
}
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[CCDirector sharedDirector] replaceScene:[NextLayer scene]];
}
使用静态方法scene来构造场景并返回。
zheteng方法负责用三亿个浮点数除法运算来模拟加载大量资源。
在屏幕的触摸中,直接使用导演的replaceScene方法切换到目标场景。
看NextLayer的代码:
// NextLayer.m
// CH05
//
// Created by phc on 11-12-12.
// Copyright 2011年 hxsoft. All rights reserved.
//
#import "NextLayer.h"
#import "LoadingScene.h"
@implementation NextLayer
+(id)scene
{
CCScene *sc = [CCScene node];
[sc addChild:[NextLayer node]];
return sc;
}
//用浮点数除法折腾
-(void)zheteng
{
float a = 2423;
float b = 3432;
for (int i = 0; i < 300000000; i++) {
a /= b;
}
}
-(id)init
{
if (self = [super init]) {
[self zheteng];
CCLabelTTF *lbl = [CCLabelTTF labelWithString:@"目标场景,点击屏幕切换场景(有loading)" fontName:@"Arial" fontSize:16];
[self addChild:lbl];
CGSize sizeOfWin = [[CCDirector sharedDirector] winSize];
lbl.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfWin.height * 0.5);
[self setIsTouchEnabled:YES];
}
return self;
}
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[CCDirector sharedDirector] replaceScene:[LoadingScene initWithTargetScene:FIRSTLAYERTARGET]];
}
@end
和FirstLayer唯一不同的就是,本层使用replaceScene切换到了LoadingScene场景,注意initWithTargetScene这个方法。他出现在LoadingScene类中:
// LoadingScene.m
// CH05
//
// Created by phc on 11-12-12.
// Copyright 2011年 hxsoft. All rights reserved.
//
#import "LoadingScene.h"
#import "FirstLayer.h"
#import "NextLayer.h"
@implementation LoadingScene
-(void)dealloc
{
[super dealloc];
}
+(id)initWithTargetScene:(targetScene)targetSc
{
return [[[self alloc] initScene:targetSc] autorelease];
}
-(id)initScene:(targetScene)targetSc
{
_targetsc = targetSc;
if (self = [super init]) {
CCLabelTTF *lbl = [CCLabelTTF labelWithString:@"Loading..." fontName:@"Marker Felt" fontSize:64];
[self addChild:lbl];
CGSize sizeOfWin = [[CCDirector sharedDirector] winSize];
lbl.position = CGPointMake(sizeOfWin.width * 0.5, sizeOfWin.height * 0.5);
[self schedule:@selector(waitLoad)];
}
return self;
}
-(void)waitLoad
{
switch (_targetsc) {
case FIRSTLAYERTARGET:
[[CCDirector sharedDirector] replaceScene:[FirstLayer scene]];
break;
case NEXTLAYERTARGET:
[[CCDirector sharedDirector] replaceScene:[NextLayer scene]];
break;
default:
break;
}
}
@end
initWithTargetScene类负责构造自身的示例,同时接收一个targetScene类型的参数来指向过渡到的目标场景,这是为了以后多场景扩展而设计的代码。
当然,targetScene不是系统提供的,他其实是我们自定义的一个枚举类型,他出现在LoadingScene.h中:
typedef enum
{
INVALIDTARGET = 0,
FIRSTLAYERTARGET = 1,
NEXTLAYERTARGET = 2
}targetScene;
我们在schedule回调的方法waitLoad中切换到目标场景。
其实这并没有从根本上解决问题,我们仅仅是做了一次中转而已。
甚至从效率上讲,因为多了一次场景切换,反而增加了系统负担。你会发现在LoadingScene中的帧率显示区依然是卡死的。但是从感觉上而言,玩家可能会觉得更容易接受。