1.11 添加视差效果
在本部分,我们将向游戏中添加视差效果(背景滚动效果),它是游戏中非常流行的一种效果。在视差效果中,相比于背景中的对象,前景中的对象移动得更快,背景中的对象移动得要慢很多,借此产生立体感与运动错觉。
1.11.1 准备工作
回想一下以前的电影片段,其中的英雄或主角保持静止不动,他们看上去就像在骑马一样,背景不断循环,让人产生错觉,以为英雄在场景中真地向前移动,如图1-31所示。
下面我们将实现一个非常简单的视差效果,其中所有的背景对象(例如,树、灌木、草)都以相同的速度进行移动。为了实现这一效果,我们只要获取背景图像,并让它在一个循环中不断移动即可。
视差效果实现如下:针对背景图像,我们将使用两个精灵,而不是一个精灵,在游戏开始时把它们沿水平方向并排放在一起,如图1-31中的第一幅图所示。第一个精灵可见,第二个精灵在屏幕之外,最初玩家并不能看到它。
当游戏开始时,两个精灵将以一定的速度朝x轴的负方向移动,即向屏幕左侧移动。两个精灵以相同的速度移动,因此当游戏开始时,精灵1将慢慢地向左逐渐移出屏幕,随之精灵2将一点点地在屏幕显现出来。
一旦精灵1完全移出屏幕,它将快速移到精灵2的右侧,即精灵2在游戏开始时所处的位置上。
上述过程将在一个循环中不断重复进行。两个精灵总是向屏幕左侧移动。当一个精灵从屏幕左侧移出屏幕之后,它将立即移动到屏幕右侧,并且继续向左一点点地移动。
在为视差滚动创建资源,编写视差效果代码时,有几点需要各位牢记。首先,当为视差效果创建资源时,所使用的图像应该是连续的。例如,当你观看前面的第二幅图像时,会看到背景中的山脉好像是连续的。即使Sprite 1与Sprite 2是两幅不同的图像,当把它们放在一起时,它们看上去就像单独的一张图像。同样的现象也出现在山脚下的淡绿色灌木丛上。灌木丛的左半部分位于Sprite 1中,右半部分位于Sprite 2中,当把它们并排在一起时,它们就会一起组成一棵完整的灌木,让人产生一种它们本来就是一棵单独灌木的错觉。
第二点要注意的是图像之间的接缝。即使把图像无缝衔接在一起,并且让精灵以相同的速度移动,有时在精灵之间仍然可能会观察到有缝隙存在。尽管这不是一个非常普遍的问题,但是在一些框架中它可能会出现。为了防止出现这一问题,你可以把图像稍微拉伸一点点,使图像精灵彼此略微发生重叠,通常玩家觉察不到这种细微的变化。另一个方法是采用手工方式把精灵放置到屏幕精灵的末端,并且必要时做适当的调整,把精灵之间的接缝弥合。
上面这些就是视差滚动效果背后涉及到的主要理论。接下来,让我们一起编写代码,实现简单的视差滚动效果。
1.11.2 操作步骤
首先,采用类似于创建Hero类的方式,创建CocosTouchClass类型的文件,并且将其命名为ParallaxSprite。
打开ParallaxSprite.h文件,添加如下代码。
#import "CCSprite.h"
@interface ParallaxSprite :CCSprite{
CGSize _winSize;
CGPoint _center;
CCSprite _sprite1, _sprite2;
float _speed;
}
-(id)initWithFilename:(NSString *)filename Speed:(float)speed;
-(void)update:(CCTime)delta;
@end
在上述代码中,我们先创建了几个变量,这些变量后面会用到,例如变量_winSize和_center,前一个变量用来获取游戏运行设备的屏幕分辨率的大小,后一个用来计算屏幕中心。
接着,我们又创建了两个CCSprite类型的变量,持有两张图像,在视差效果中用来不断循环。
然后,我们添加了一个_speed变量,用来指定图像移动与循环的速度。
类似于Hero类,在ParallaxSprite类中,我们也创建了一个initWithFilename函数,它使用给定的文件名对类进行初始化。另外,我们也添加了一个float类型变量,用来指定精灵的速度。
此外,我们还需要一个update函数,它在 1 秒内会被调用60次,用来在类中更新两个精灵的位置。
以上就是ParallaxSprite.h文件的所有代码,接下来,转到并打开ParallaxSprite.m文件。
在ParallaxSprite.m文件中,添加如下代码:
#import "ParallaxSprite.h"
@implementation ParallaxSprite
-(id)initWithFilename:(NSString *)filename Speed:(float)speed;{
if(self = [super init]){
NSLog(@"[parallaxSprite] (init) ");
_winSize = [[CCDirectorsharedDirector]viewSize];
_center = CGPointMake(_winSize.width/2, _winSize.height/2);
_speed = speed;
_sprite1 = [CCSpritespriteWithImageNamed:filename];
_sprite1.position = _center;
[selfaddChild:_sprite1];
_sprite2 = [CCSpritespriteWithImageNamed:filename];
_sprite2.position = CGPointMake(_sprite1.position.x + _winSize.
width
, _center.y);
[selfaddChild:_sprite2];
}
return self;
}
在上述代码中,我们首先实现initWithFilename函数。在initWithFilename函数中,先初始化超类,获取_winSize。接着,通过把窗口的宽度与高度分别除以2计算出屏幕中心,再把speed的值赋给_speed变量。
然后,创建_sprite1和_sprite2两个变量,在spriteWithImageNames中,通过filename变量传入文件名字符串。
请注意,_sprite1被放置到屏幕中心,_sprite2被设置到屏幕之外,横坐标与_spirte1相差一个屏幕宽度,纵坐标与_spirte1相同。
最后,把两个精灵添加到类中。
接下来,我们开始实现update函数,添加代码如下:
-(void)update:(CCTime)delta{
floatxPos1 = _sprite1.position.x - _speed;
floatxPos2 = _sprite2.position.x - _speed;
_sprite1.position = CGPointMake(xPos1, _sprite1.position.y);
_sprite2.position = CGPointMake(xPos2, _sprite1.position.y);
if(xPos1 + _winSize.width/2 <= 0){
_sprite1.position = CGPointMake(_sprite2.position.x +
_winSize.width, _center.y);
}else if(xPos2 + _winSize.width/2 <= 0){
_sprite2.position = CGPointMake(_sprite1.position.x + _winSize.
width
, _center.y);
}
}
@end
首先,我们分别为两个精灵计算它们在x轴上的新位置,计算时先获取精灵当前位置的x值,再用它减去精灵的移动速度。之所以这样做,是因为我们希望在每次调用update函数时让精灵沿着x轴的负方向进行移动。
接着,我们把新坐标分别指派给两个精灵,其中x值为上面计算得到的值,y值保持原值不变。
然后,检测图像的右边缘对于玩家是否仍然可见,还是已经移出屏幕左侧之外。如果是这样,我们就把精灵放到脱屏位置上,即纵坐标不变,横坐标与另一个精灵相距一个屏幕宽度,以确保两个精灵之间不会出现缝隙。
在代码中,我们使用了if-else语句,这是因为每次只会有一个精灵移出屏幕左侧边界。
1.11.3 工作原理
下面让我们一起看一下如何使用ParallaxSprite类。在MainScene.h类中,引入ParallaxSprite.h文件,创建一个ParallaxSprite类型的变量pSprite,代码如下:
#import "Hero.h"
#import "ParallaxSprite.h"
@interface MainScene :CCNode{
CGSizewinSize;
Hero* hero;
ParallaxSprite* pSprite;
}
然后,在MainScene.m文件中,移除本章开始时用来添加背景精灵的代码,添加如下代码:
//Basic CCSprite - Background Image - REMOVE
//CCSprite* backgroundImage = [CCSpritespriteWithImageNamed:@"Bg.
png"];
//backgroundImage.position = CGPointMake(winSize.width/2,
winSize.height/2);
//[self addChild:backgroundImage];
//Parallax Background Sprite - ADD
pSprite = [[ParallaxSpritealloc]initWithFilename:@"Bg.png" Speed:5];
[selfaddChild:pSprite];
正如前面我们所做的那样,我们把Bg.png文件指派给pSprite,此外,我们又指定了速度值为5。
请注意,不必手工调用ParallaxSprite类的update函数,每一帧它都会被自动调用执行。而且,你也不必像以前那样调度它,开始时update函数会被自动初始化。
到此为止,我们已经编写好了所有代码,运行代码,我们将会看到如图1-32所示的背景滚动效果。