当前位置:文档之家› Cocos2d-X背景重复贴图

Cocos2d-X背景重复贴图

Cocos2d-X背景重复贴图

CCSize winSize = CCDirector::sharedDirector()->getWinSize();//获得屏幕尺寸,这里要画个和屏幕等大的静态背景

CCRect r(0, 0, winSize.width, winSize.height);

CCSprite* shelfBG = CCSprite::spriteWithFile(RES_BOOK_SHELF_BG, r);// 创建sprite纹理指定循环图片,大小等同屏幕

ccTexParams tp = {GL_LINEAR, GL_LINEAR, GL_REPEAT,GL_REPEAT};// 主要用到的是这个,水平重复平铺,垂直重复平铺

shelfBG->getTexture()->setTexParameters(&tp);

shelfBG->setPosition(ccp(winSize.width/2, winSize.height/2));

this->addChild(shelfBG);// 添加sprite节点到layer

图片资源宽高必须是2的n次幂,我用的是128x128(像素)

程序画出来的效果:

============================还是割了吧

=========================================

参考:https://www.doczj.com/doc/604976369.html,/encounter/archive/2011/06/01/2188485.html

Learn IPhoneand iPad Cocos2d Game Delevopment》第7章(原文中有部分无关紧要的内容未进行翻译)。

对于射击类游戏,使用重力感应进行游戏控制是不可接受的,采用虚拟手柄将会更恰当。出于“不重新发明轮子”的原则,我们将采用开源库SneakyInput。

控制玩家的飞船进行移动只是其中一件事情。我们还需要让背景能够滚动,以造成在某个方向上“前进”的感觉。为此必须自己实现背景滚动。由于CCParallaxNode的限制,它不能无限制地滚动卷轴式背景。

一、高级平行视差滚动

在这个射击游戏中,我们将使用ParallaxBackground节点。同时,我们将使用CCSpriteBatchNode以提高背景图片的渲染速度。

1、创建背景层

下图显示了我用Seashore绘制背景层。

每个背景层位于Seashore的单独的图层中,每一层可以保存为单独的文件,分别命名为bg0-bg6。

以这种方式创建背景层的原因在于:你既可以把各个层的背景放在一起,也可以分别把每一层存成单独的文件。所有文件大小都是480*320,似乎有点浪费。但不需要把单独把每个文件加到游戏里,只需要把它们融合在一个贴图集里。由于Zwoptex会自动去除每个图片的透明边沿,它会把这些背景层紧紧地放到一起没有丝毫空间的浪费。

把背景分层的原因不仅是便于把每一层放在不同的Z轴。严格讲,bg5.png(位于最下端)和bg6.png(位于最上端)应该是相同的Z坐标,因为它们之间没有交叠,所以我把他们存在分开的文件里。这样Zwoptex会把两者上下之间的空白空间截掉。

此外,把背景分层有利于提高帧率。iOS设备的填充率很低(每1帧能绘制的像素点数量)。由于不同图片之间常存在交叠的部分,iOS设备每1帧经常需要在同1点上绘制多次。比如,最极端的情况,一张全屏图片位于另一张全屏图片之上。你明明只能看到最上面的图片,但设备却不得不两张图片都绘制出来。这种情况叫做overdraw(无效绘制)。把背景分层可以尽量地减少无效绘制。

2、修改背景的绘制

#import

#import "cocos2d.h"

@interface ParallaxBackground :CCNode

{

CCSpriteBatchNode*spriteBatch;

int numStripes;

CCArray* speedFactors; // 速度系数数组

float scrollSpeed;

}

@end

我把CCSpriteBatchNode引用保存在成员变量里,因为它在后面会用得比较频繁。采用成员变量访问节点比通过getNodeByTag方式访问要快一点,每1帧都会节约几个时钟周期(但保留几百个成员变量就太夸张了)。

#import "ParallaxBackground.h"

@implementation ParallaxBackground

-(id) init

{

if ((self = [super init]))

{

CGSize screenSize = [[CCDirector sharedDirector]winSize];

// 把game_art.png加载到贴图缓存

CCTexture2D* gameArtTexture =

[[CCTextureCache sharedTextureCache]addImage:@"game-art.png"];

// 初始化CCSpriteBatchNodespritebatch

spriteBatch = [CCSpriteBatchNode batchNodeWithTexture:gameArtTexture]; [self addChild:spriteBatch];

numStripes = 7;

// 从贴图集中加载7张图片并进行定位

for (int i =0; i < numStripes; i++)

{

NSString* frameName = [NSString stringWithFormat:@"bg%i.png", i]; CCSprite* sprite = [CCSprite spriteWithSpriteFrameName:frameName]; sprite.position =CGPointMake(screenSize.width /2, screenSize.height /2); [spriteBatch addChild:sprite z:i tag:i];

}

// 再加7个背景层,将其翻转并放到下一个屏幕位置的中心for (int i =0; i < numStripes; i++) {

NSString* frameName = [NSString stringWithFormat:@"bg%i.png", i];

CCSprite* sprite = [CCSprite spriteWithSpriteFrameName:frameName];

//放到下一屏的中心

sprite.position =CGPointMake(screenSize.width /2 + screenSize.width,

screenSize.height /2);

//水平翻转

sprite.flipX =YES;

[spriteBatch addChild:sprite z:i tag:i + numStripes];

}

// 初始化速度系数数组,分别定义每一层的滚动速度speedFactors =

[[CCArray alloc]initWithCapacity:numStripes];

[speedFactors addObject:[NSNumber numberWithFloat:0.3f]];

[speedFactors addObject:[NSNumber numberWithFloat:0.5f]];

[speedFactors addObject:[NSNumber numberWithFloat:0.5f]];

[speedFactors addObject:[NSNumber numberWithFloat:0.8f]];

[speedFactors addObject:[NSNumber numberWithFloat:0.8f]];

[speedFactors addObject:[NSNumber numberWithFloat:1.2f]];

[speedFactors addObject:[NSNumber numberWithFloat:1.2f]];

NSAssert([speedFactors count] ==numStripes, @"speedFactors count does notmatch numStripes!");

scrollSpeed = 1.0f;

[self scheduleUpdate];

}

returnself;

}

-(void) dealloc

{

[speedFactors release];

[super dealloc];

}

-(void) update:(ccTime)delta

{

CCSprite* sprite;

CCARRAY_FOREACH([spriteBatch children], sprite)

{

NSNumber* factor = [speedFactors objectAtIndex:sprite.zOrder];

CGPoint pos = sprite.position;

pos.x -= scrollSpeed * [factor floatValue];

sprite.position = pos;

}

}

@end

在GameScene中,我们曾经加载了贴图集game-art.plist:

CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

[frameCache addSpriteFramesWithFile:@"game-art.plist"];

因此,实际上game-art.png已经加载。当我们在init方法中再次加载game-art.png时(我们需要获得一个CCTexture2D以构造CCSpriteBatchNode),实际上并不会再次加载game-art.png,CCTextureCache会从缓存中返回一个已经加载的CCTexture2D对象。我们没有其他办法,因为cocos2d没有提供一个getTextureByName 的方法。

接下来,初始化了CCSpriteBatchNode对象,并从贴图集中加载了7张背景图。

在update方法中,每一层背景图的x位置每播放一帧,就减去了一点(从右向左移动)。移动的距离由scrollSpeed*一个速度系数(speedFactors数组中相应的一个数值)来计算。

这样,每1层的背景会有不同的速度系数,从而会以不同的速度移动:

由于各层移动速度不同,所以最终背景的右边沿会呈现出不整齐的现象:

3、无限滚动

在ParallaxBackground 类的init方法中,我们再次添加了7张背景图并进行水平翻转。目的是让每一层背景图片的宽度在水平方向上延伸,翻转的目的则是使拼在一起的时候两张图片的对接边沿能够对齐。

同时,把第2幅图片紧挨着放在第1幅图右边,从而把两张相同但互为镜像的图片拼接在一起。

这是第1幅图的位置摆放:

sprite.position =CGPointMake(screenSize.width /2, screenSize.height /2);

这是第2幅图的位置摆放:

// 放到下一屏的中心

sprite.position =CGPointMake(screenSize.width /2 + screenSize.width,

screenSize.height /2);

通过比较很容易就得以看出二者的x坐标相差1个屏幕宽度:screenSize(这同时也是图片宽度,我们的图片是严格按照480*320的屏幕尺寸制作的)。

下面我们可以用另外一种方式来摆放图片(更直观),把相应的代码修改为:

第1幅图的摆放:

sprite.anchorPoint =CGPointMake(0,0.5f);

sprite.position =CGPointMake(0,screenSize.height /2);

第2幅图的摆放:

sprite.anchorPoint =CGPointMake(0,0.5f);

sprite.position =CGPointMake(screenSize.width,screenSize.height /2);

我们改变了图片的anchorPoint属性。anchorPoint就是一个图形对象“锚点”或“对齐点”,这个属性对于静止不动的对象是没有意义的。但对于可以移动的对象来说,意味着位置移动的参考点。也就是说物体移动后锚点应该和目标点对齐(定点停车?)。如果命令一个物体移

动到a点,真实的意思其实是把这个物体的锚点和a点对齐。锚点用一个CGPoint表示,不过这个CGPoint的x和y值都是0-1之间的小数值。一个物体的锚点,如果不改变它的话,默认是(0.5f, 0.5f)。这两个浮点数所代表的含义是:该锚点位于物体宽度1/2和高度1/2的地方。即物体(图形)的正中心:

而代码 sprite.anchorPoint =CGPointMake(0,0.5f);实际上是把图片的锚点移到了图片左中部的位置:

这样我们摆放第1张图时候可以从横坐标0开始摆,而不必要计算屏幕宽度。

而摆放第2张图的时候直接从第2屏的起始位置(即1个屏幕宽度)开始摆。

接下来,我们可以修改update的代码,让两幅图交替移动以模拟出背景图无限滚动的效果:-(void) update:(ccTime)delta

{

CCSprite* sprite;

CCARRAY_FOREACH([spriteBatch children], sprite)

{

NSNumber* factor = [speedFactors objectAtIndex:sprite.zOrder];

CGPoint pos = sprite.position;

pos.x -= scrollSpeed * [factor floatValue];

// 当有一副图移出屏幕左边后,把它挪到屏幕右边等待再次滚动—无限滚动

if (pos.x < -screenSize.width)

{

pos.x+= screenSize.width *2 - 1;

}

sprite.position = pos;

}

}

实际上,飞船是不动的,动的是背景,以此模拟出飞船在游戏世界中前进的效果。

4、防止抖动

仔细观察,你会发现画面上有时会出现一条黑色的竖线。这是由于图片之间拼接位置出现凑整的问题。帧与帧之间,由于小数点上的误差,有时会出现1个像素宽度的缝隙。对于商业品质的游戏,应该解决这个小问题。

最简单的办法,让图片之间微微交叠1个像素。

在摆放第2幅图时:

sprite.position =CGPointMake(screenSize.width-1,screenSize.height /2);

在update方法中:

// 当有一副图移出屏幕左边后,把它挪到屏幕右边等待再次滚动—无限滚动

if (pos.x < -screenSize.width)

{

pos.x+= screenSize.width *2 - 2;

}

sprite.position = pos;

为什么是减2个像素?因为1个像素是上次拼接时“用掉”的(一开始我们在init的时候就拼接过一次)。而在update方法中,已经是第2次拼接了。1次拼接需要1个像素,两次拼接自然要2个像素。

5、重复贴图

在这一章没有其他值得注意的技巧了。你可以让同一个贴图在任意一个空间里重复。只要这个空间够大,你能让这个贴图没完没了地重复。至少成千上万像素或成打的屏幕上能够用一张贴图贴满,而不会给性能和内存带来不良影响。

这个技巧就是使用OpenGL 的GL_REPEAT参数。只不过,要重复的对象只能是边长为2的n次方的正方形。如32*32,128*128。

CGRect repeatRect = CGRectMake(-5000, -5000, 5000,5000);

CCSprite* sprite = [CCSpritespriteWithFile:@”square.png” rect:repeatRect];

ccTexParams params ={

GL_LINEAR,

GL_LINEAR,

GL_REPEAT,

GL_REPEAT

};

[sprite.texture setTexParameters:¶ms];

这里,CCSprite必须用一个CGRect构造,这个CGRect描述了要重复贴图的矩形范围。ccTexParams参数是一个GL_REPEAT结构,这个参数用于CCTexture2D的setTexParameters方法。

这将使整个指定的矩形区域被square.png图片铺满(横向平铺,纵向平铺)。当你移动CCSprite时,整个贴图局域也被移动。你可以用这个技巧把最底层的背景删除,然后用一张简单的小图片替代。

二、虚拟手柄

由于iOS设备没有按钮(除了Home键),虚拟手柄(或D-pads)在游戏中就显得很有用。

1、SneakyInput介绍

SneakyInput的作者是Nick Pannuto,示例代码由CJ Hanson提供。这是一个免费的开源项目,它接受自愿捐助:https://www.doczj.com/doc/604976369.html,/campaigns/9124

该项目源码托管于github库:

https://www.doczj.com/doc/604976369.html,/sneakyness/SneakyInput.

源码下载后,解包,打开该项目,编译运行。你可以在模拟器中看到一个虚拟手柄。

SneakyInput中集成了cocos2d,但可能不是最新版本。如果出现”base SDKmissing”错误,你可以修改Info面板中的base SDK。

2、集成SneakyInput

对于源代码项目,有这样一个问题:当我们需要和其他项目集成时,哪些文件是必须的?每个源码项目都不一样,答案也不尽相同。

但我会告诉你SneakyInput的哪些文件是必须的,包括5个核心的类:

SneakyButton 和SneakyButtonSkinnedBase

SneakyJoystick 和SneakyJoystickSkinnedBase

ColoredCircleSprite(可选的)

其他文件不是必须的,但可作为一些参考。

使用Add Existing Files对话框加入上述5个类(5个.m文件,5个.h文件)。

3、射击按钮

首先,我们需要在GameScene的scene方法中加入一个InputLayer(继承自CCLayer):InputLayer* inputLayer = [InputLayer node];

[scene addChild:inputLayer z:1tag:GameSceneLayerTagInput];

在枚举GameSceneLayerTags中添加GameSceneLayerTagInput定义,用于InputLayer 层的tag:

typedefenum

{

GameSceneLayerTagGame =1,

GameSceneLayerTagInput,

} GameSceneLayerTags;

然后新建类InputLayer:

#import

#import "cocos2d.h"

// SneakyInputheaders

#import "ColoredCircleSprite.h"

#import "SneakyButton.h"

#import "SneakyButtonSkinnedBase.h" #import "SneakyJoystick.h"

#import "SneakyJoystickSkinnedBase.h" #import "SneakyExtensions.h"

@interface InputLayer : CCLayer

{

SneakyButton* fireButton;

}

@end

#import "InputLayer.h"

#import "GameScene.h"

@interface InputLayer(PrivateMethods) -(void) addFireButton;

@end

@implementation InputLayer

-(id) init

{

if ((self = [super init]))

{

[self addFireButton];

[self scheduleUpdate];

}

returnself;

}

-(void) dealloc

{

[super dealloc];

}

-(void) addFireButton

{

float buttonRadius = 80;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

fireButton = [[[SneakyButton alloc] initWithRect:CGRectZero]autorelease]; fireButton.radius = buttonRadius;

fireButton.position =CGPointMake(screenSize.width - buttonRadius,buttonRadius); [self addChild:fireButton];

}

-(void) update:(ccTime)delta

{

if(fireButton.active) {

CCLOG(@"FIRE!!!");

}

}

@end

在头文件中,我们定义了一个Sneakbutton成员变量。然后我们通过addFireButton方法创建发射按钮。

因为SneakyButton的initWithRect方法的CGRect参数其实并没有用到,所以我们可以简单地传递一个CGRectZero给它。实际上SneakyButton使用radius属性代表触摸所能响应的圆形半径,我们通过简单计算(屏幕宽度-按钮半径)把射击按钮紧凑地放到屏幕的右下角。

接下来,[self shceduleUpdate]调用了update方法。

在update方法里,我简单地在Log里输出一句话,以代替射击动作。

4、订制按钮外观

我用了一个特殊的类别(Category),为SneakyButton增加了一个两个特殊的静态初始化方法,以防止你忘记alloc或者autorelease对象。如SneakyExtensions.h和SneakyExtensions.m所示:

#import "ColoredCircleSprite.h"

#import "SneakyButton.h"

#import "SneakyButtonSkinnedBase.h"

#import "SneakyJoystick.h"

#import "SneakyJoystickSkinnedBase.h"

@interface SneakyButton(Extension)

+(id) button;

+(id) buttonWithRect:(CGRect)rect;

@end

@interface SneakyButtonSkinnedBase (Extension)

+(id) skinnedButton;

@end

#import "SneakyExtensions.h"

@implementation SneakyButton(Extension)

+(id) button

{

return [[[SneakyButton alloc]initWithRect:CGRectZero]autorelease]; }

+(id) buttonWithRect:(CGRect)rect

{

return [[[SneakyButton alloc]initWithRect:rect]autorelease];

}

相关主题
文本预览
相关文档 最新文档