我们接着上一篇文章继续做我们的GameScene。
首先,我们来制作游戏的背景。这里我们希望我们的游戏背景不是那种死气沉沉的一动不动的背景,最好是动态的,并且有点层次感的背景(好吧我承认,因为我做的是这样的,我才这么说的T_T)。
我们观察一下之前游戏截图中的背景,里面有天空,有山,有星星,有云层,而且仔细观察,山是分近景的山和远景的山的,在生活中我们也知道,坐汽车的时候,你看窗外的景物,离你越近的景物随着车的运动向后移动的越快,越远的景物向后移动的越慢,我们游戏中的背景也希望给玩家这种感觉,所以我们分了很多层,按照离我们由远及近的顺序依次是天空->星星->云层->远山->近山。我们在游戏中通过这些景物向后移动,造成飞船飞行的错觉。
下面这些图片是各个层的图片:
front_mountains.png:
background_mountains.png:
clouds.png:
stars.png(星星这个层是一些外发光的黄点,不是很明显,可以自己用PS制作一张,尺寸为2048?×?1536):
sky.png(一张深色的渐变背景,尺寸为1×2048,可以用PS的渐变工具做一张orz
)
我们将这5张图片添加到工程的Resources文件夹中(添加的时候记得要勾选Copy items into destinationgroup’s folder(if needed),这个选项会将图片拷贝到工程自己的文件夹中,如果不勾选,只会添加引用):
下面我们开始编写代码,实现动态背景层效果。
先考虑一个细节问题,既然我们分近山远山了,那么是不是可以让飞船穿梭在近山与远山之间呢?要实现这样的效果很简单,只需要在添加节点的时候,注意节点的z-index,让飞船的z-index在远山和近山的index之间即可。
同时,我们再创建一个TagDefinitions.h文件,用来存放各种元素的Tag(这样是为了保证我们在调用addChild:z:tag:方法传入tag的时候,能够传入一个有意义的枚举参数而不是一个没有任何意义又难记的数字!同时也保证在相同意义的上下文中,我们不会使用相同的tag值导致getChildByTag方法无法取出正确的对象),在TagDefintions.h中添加如下代码(都是一些代码中用到的Tag的枚举值,后面代码中出现的时候就不解释了):
#ifndef DarkTravel_TagDefinitions_h
#define DarkTravel_TagDefinitions_h
typedef enum{
GameDifficultyEasy = 1,
GameDifficultyNormal,
GameDifficultyHard,
} GameDifficulty;
typedef enum{
TargetSceneGameScene,
TargetSceneGameoverScene,
} TargetScene;
typedef enum{
AirCraftTag,
Stars1Tag,
Stars2Tag,
Clouds1Tag,
Clouds2Tag,
FrontMountains1Tag,
FrontMountains2Tag,
BackgroundMountains1Tag,
BackgroundMountains2Tag,
ParallaxSceneTagParallaxNodeTag,
BulletCacheManagerTag,
EnemyShipCacheManagerTag,
ScoreLabelTag,
WaveLabelTag,
HighScoreLabelTag,
//always add tags before this tag
EnemyShipStartTag,
} ParallaxSceneTags;
typedef enum{
StartLayerTouchToPlayLB,
GameOverLayerTouchToPlayLB,
} StartLayerElements;
typedef enum{
PoweredBullet,
SmallRoundBullet,
SmallEnemyBullet,
BigEnemyBullet,
BOSSBullet,
PoweredEnemyBullet,
} BulletTypes;
typedef enum{
SpeedKiller,
LittleWorm,
BigDaddy,
PowerMaker,
BOSS,
} EnemyShipTypes;
typedef enum{
GameSceneLayerTagGame = 1,
GameSceneLayerTagInput
} GameSceneLayerTags;
#endif
另外,我们再创建一个CommonUtility类,作为一个工具类,用来获取屏幕长宽,屏幕中点,各种区域大小等信息:
CommonUtility.h文件:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface CommonUtility : CCNode{
float screenWidth;
float screenHeight;
CGRect screenRect;
float enemyMovementSmallAreaWidth;
float enemyMovementAreaHeight;
float enemyMovementLargeAreaWidth;
float screenScale;
}
+ (CommonUtility*)utility;
@property (readonly) float screenWidth;
@property (readonly) float screenHeight;
@property (readonly) CGRect screenRect;
@property (readonly) floatenemyMovementSmallAreaWidth;
@property (readonly) float enemyMovementAreaHeight;
@property (readonly) floatenemyMovementLargeAreaWidth;
@property float screenScale;
@property (readonly) CGPoint screenCenter;
@end
CommonUtility.m文件:
#import "CommonUtility.h"
@implementation CommonUtility
static CommonUtility* utilityData;
+ (CommonUtility*)utility{
if (utilityData == nil) {
utilityData= [[[CommonUtility alloc] init] autorelease];
}
return utilityData;
} //utility
- (id)init{
if (self = [super init]) {
CGSizescreenSize = [[CCDirector sharedDirector] winSize];
screenWidth= screenSize.width;
screenHeight = screenSize.height;
screenRect= CGRectMake(0, 0, screenWidth, screenHeight);
enemyMovementSmallAreaWidth = 300;
enemyMovementAreaHeight = 200;
enemyMovementLargeAreaWidth = 400;
screenScale= 0.5;
}
return self;
} //init
- (float)screenHeight{
return screenHeight;
} //screenHeight
- (float)screenWidth{
return screenWidth;
} //screenwidth
- (CGRect)screenRect{
return screenRect;
} //screenRect
- (float)enemyMovementSmallAreaWidth{
return self.screenWidth -enemyMovementSmallAreaWidth;
} //enemyMovementSmallAreaWidth
- (float)enemyMovementAreaHeight{
return enemyMovementAreaHeight *screenScale;
} //enemyMovementAreaHeight
- (float)enemyMovementLargeAreaWidth{
return self.screenWidth -enemyMovementLargeAreaWidth;
} //enemyMovementLargeAreaWidth
- (float)screenScale{
return screenScale;
} //screenScale
- (void)setScreenScale:(float)ss{
screenScale = ss;
} //setScreenScale
- (CGPoint)screenCenter{
return ccp(screenWidth*0.5f,screenHeight*0.5f);
}
- (void)dealloc{
[utilityData release];
utilityData = nil;
[super dealloc];
} //dealloc
@end
CommonUtility类的静态方法返回一个类的实例,这个方法保证CommonUtility类仅被初始化一次,避免重复计算。类的实现方法中有几点需要说明的:
实例化语句“[[[CommonUtility alloc] init] autorelease]”中的autorelease方法是符合Cocos2d对象的初始化习惯,Cocos2d中的对象都是autorelease的,以减少程序员的编码负担,利用XCode的ARC机制(内存计数回收机制,当一个对象的引用数为0时,释放该对象)。
在dealloc中释放静态对象并将指针指向nil,以释放对象的引用计数。
CGSize screenSize = [[CCDirector sharedDirector]winSize];这句通过CCDirector的公共实例的winSize方法获取屏幕大小。
接着,我们在GameLayer.m中重载init方法(init方法在调用CCNode的node方法时会被调用):
- (id)init{
if (self = [super init]) {
[selfinitBackground];
[selfscheduleUpdate];
}
return self;
}
首先初始化self对象,调用父类的init方法,然后调用initBackground方法初始化背景元素,然后调用scheduleUpdate方法设置更新。
InitBackground方法:
- (void)initBackground{
CCSprite* clouds = [CCSpritespriteWithFile:@"clouds.png"];
CCSprite* clouds1 = [CCSpritespriteWithFile:@"clouds.png"];
CCSprite* frontMountains =[CCSpritespriteWithFile:@"front_mountains.png"]; //movesfastest
CCSprite* frontMountains1 =[CCSpritespriteWithFile:@"front_mountains.png"]; //movesfastest
float scale = [CommonUtilityutility].screenWidth / frontMountains.contentSize.width;
clouds.anchorPoint = ccp(0,0);
clouds.position = ccp(0,[CommonUtility utility].screenHeight-240*scale);
clouds1.anchorPoint = ccp(0,0);
clouds1.position =ccp(frontMountains.contentSize.width, [CommonUtilityutility].screenHeight-240*scale);
frontMountains.anchorPoint =ccp(0, 0);
frontMountains1.anchorPoint =ccp(0, 0);
frontMountains1.position =ccp(frontMountains.contentSize.width, 0);
[self addChild:clouds z:1tag:Clouds1Tag];
[self addChild:clouds1 z:1tag:Clouds2Tag];
[self addChild:frontMountainsz:4 tag:FrontMountains1Tag];
[self addChild:frontMountains1z:4 tag:FrontMountains2Tag];
CGRect repeatRect =CGRectMake(0, 0, 2048, 2048);
CCSprite* background =[CCSprite spriteWithFile:@"sky.png"rect:repeatRect]; //never moves
ccTexParams texParams = {
GL_LINEAR,
GL_LINEAR,
GL_REPEAT,
GL_REPEAT
};
[background.texturesetTexParameters:&texParams];
CCSprite* stars = [CCSpritespriteWithFile:@"stars.png"]; //moves lowest
CCSprite* stars1 = [CCSpritespriteWithFile:@"stars.png"]; //moves lowest
CCSprite* backgroundMountains= [CCSprite spriteWithFile:@"background_mountains.png"];
CCSprite* backgroundMountains1= [CCSprite spriteWithFile:@"background_mountains.png"];
[CommonUtilityutility].screenScale = 1024 / [backgroundMountains contentSize].width;
background.anchorPoint =ccp(0, 0);
stars.anchorPoint = ccp(0, 0);
stars1.anchorPoint = ccp(0,0);
stars1.position =ccp(stars.contentSize.width, 0);
backgroundMountains.anchorPoint= ccp(0, 0);
backgroundMountains1.anchorPoint= ccp(0, 0);
backgroundMountains1.position= ccp(backgroundMountains.contentSize.width, 0);
[self addChild:backgroundz:-1];
[self addChild:stars z:0tag:Stars1Tag];
[self addChild:stars1 z:0tag:Stars2Tag];
[selfaddChild:backgroundMountains z:2 tag:BackgroundMountains1Tag];
[selfaddChild:backgroundMountains1 z:2 tag:BackgroundMountains2Tag];
}
我们用图片初始化CCSprite(精灵)对象,精灵对象可以理解为场景中的各种元素,我们可以为精灵对象指定各种动作(动画),为精灵对象指定帧动画等等,这里我们用静态图片初始化精灵后,设置精灵的位置和锚点(锚点是一个对象(0,0)坐标对齐的那个点),将云、星星、山等精灵对象布置到场景中的对应位置,通过addChild方法将他们加入到场景中。
这里你可能会奇怪,为什么我们每种元素都添加了两个精灵,比如我们添加了star1和star2,仔细对比一下star1和star2的初始位置以及大小,你会发现,star1占满屏幕,star2在屏幕外边紧贴着star1右边显示。还记得我们是要让背景向左移动来模拟飞行对吧,我们的背景只有屏幕大小,不可能做一张无限大的背景,所以我们可以通过两张左右无缝对接的相同背景图片来造成一种背景循环滚动的假象,两张背景一起向左移动,当左侧的背景移出屏幕后,立刻将它放置到右侧,如此往复。背景里面的山、星星和云都是这个原理,因此每个元素都是用两个精灵组成的。再注意一下背景天空元素的添加方法,我们天空的图片是一张只有1px宽的渐变图片,我们通过横向的重复这个图片,形成我们的背景层。
好了,元素初始化完成之后,我们来重载update方法。在init方法调用scheduleUpdate方法后,程序运行的每一帧都会调用update方法。我们在update方法中更新各个元素的位置:
- (void)update:(ccTime)delta{
float sceneWidth = [[selfgetChildByTag:FrontMountains1Tag] contentSize].width;
float frontMountainMove =[CommonUtility utility].screenWidth/10*delta;
float cloudsMove =frontMountainMove/30;
[self updateSceneLocation:[selfgetChildByTag:FrontMountains1Tag] movedLength:frontMountainMovesceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:FrontMountains2Tag] movedLength:frontMountainMovesceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:Clouds1Tag] movedLength:cloudsMove sceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:Clouds2Tag] movedLength:cloudsMove sceneLength:sceneWidth];
float frontMountainMove1 =[CommonUtility utility].screenWidth/10*delta;
float backgrounMountainMove1 =frontMountainMove1/3;
float starsMove1 =frontMountainMove1/100;
[self updateSceneLocation:[selfgetChildByTag:BackgroundMountains1Tag] movedLength:backgrounMountainMove1sceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:BackgroundMountains2Tag] movedLength:backgrounMountainMove1sceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:Stars1Tag] movedLength:starsMove1 sceneLength:sceneWidth];
[self updateSceneLocation:[selfgetChildByTag:Stars2Tag] movedLength:starsMove1 sceneLength:sceneWidth];
}
这里我们看下update方法的参数,delta表示上一帧到这一帧的时间间隔,默认Cocos2d的更新频率是每秒60次(在AppDelegate中初始化),所以理论上,每个delta值是1/60秒,但是实际运行中,这个频率是个变量,所以delta值也是一个不定值,我们通过这个不定值计算出这段时间各个元素的移动距离,然后用updateSceneLocation来更新元素的位置,updateSceneLocation方法实现如下:
- (void)updateSceneLocation:(CCNode*)node
movedLength:(float)movedLength
sceneLength:(float)sceneLength{
CGPoint curPos = node.position;
if (curPos.x - movedLength <-sceneLength) {
[nodesetPosition:ccp(curPos.x - movedLength + sceneLength * 2, curPos.y)];
}else{
[nodesetPosition:ccp(curPos.x - movedLength, curPos.y)];
}
}
updateSceneLocation方法判断元素右边是否超出屏幕左侧,如果超出了之后,将该元素移动到屏幕右侧。
这样我们就完成了滚动背景的制作,现在编译运行,效果还不错吧,如果觉得速度不满意,可以调整update方法中的相对速率。
这里我们就制作完成了背景层。
下一篇我们就准备添加我们的飞船了。