Cocos2d-x 背景重复贴图

来源:互联网 发布:映射网络驱动器端口号 编辑:程序博客网 时间:2024/06/05 22:36


        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(像素)


程序画出来的效果:



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


参考:http://www.cnblogs.com/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 <Foundation/Foundation.h>

#import "cocos2d.h"

 

@interface ParallaxBackground :CCNode

{

CCSpriteBatchNode*spriteBatch;

 

intnumStripes;

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

floatscrollSpeed;

}

 

@end

 

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

 

#import "ParallaxBackground.h"

 

@implementation ParallaxBackground

 

-(id) init

{

if ((self = [superinit]))

{

CGSize screenSize = [[CCDirectorsharedDirector]winSize];

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

CCTexture2D* gameArtTexture = [[CCTextureCachesharedTextureCache]addImage:@"game-art.png"];

// 初始化CCSpriteBatchNodespritebatch

spriteBatch = [CCSpriteBatchNodebatchNodeWithTexture:gameArtTexture];

[selfaddChild:spriteBatch];

 

numStripes = 7;

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

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

{

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

CCSprite* sprite = [CCSpritespriteWithSpriteFrameName:frameName];

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

[spriteBatchaddChild:spritez:itag:i];

}

 

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

{

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

CCSprite* sprite = [CCSpritespriteWithSpriteFrameName:frameName];

//放到下一屏的中心

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

 

//水平翻转

sprite.flipX =YES;

[spriteBatchaddChild:spritez:itag:i + numStripes];

}

// 初始化速度系数数组,分别定义每一层的滚动速度speedFactors = [[CCArrayalloc]initWithCapacity:numStripes];

[speedFactorsaddObject:[NSNumbernumberWithFloat:0.3f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:0.5f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:0.5f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:0.8f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:0.8f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:1.2f]];

[speedFactorsaddObject:[NSNumbernumberWithFloat:1.2f]];

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

 

scrollSpeed = 1.0f;

[selfscheduleUpdate];

}

returnself;

}

 

-(void) dealloc

{

[speedFactorsrelease];

[superdealloc];

}

 

-(void) update:(ccTime)delta

{

CCSprite* sprite;

CCARRAY_FOREACH([spriteBatchchildren], sprite)

{

NSNumber* factor = [speedFactorsobjectAtIndex:sprite.zOrder];

CGPoint pos = sprite.position;

pos.x -= scrollSpeed * [factorfloatValue];

sprite.position = pos;

}

}

 

@end

 

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

CCSpriteFrameCache* frameCache = [CCSpriteFrameCachesharedSpriteFrameCache];

[frameCacheaddSpriteFramesWithFile:@"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([spriteBatchchildren], sprite)

{

NSNumber* factor = [speedFactorsobjectAtIndex:sprite.zOrder];

CGPoint pos = sprite.position;

pos.x -= scrollSpeed * [factorfloatValue];

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

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:&params];

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

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

二、虚拟手柄

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

1、SneakyInput介绍

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

该项目源码托管于github库:

http://github.com/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 = [InputLayernode];

[scene addChild:inputLayerz:1tag:GameSceneLayerTagInput];

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

typedefenum

{

GameSceneLayerTagGame =1,

GameSceneLayerTagInput,

} GameSceneLayerTags;

 

然后新建类InputLayer:

 

#import <Foundation/Foundation.h>

#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 = [superinit]))

{

[selfaddFireButton];

[selfscheduleUpdate];

}

returnself;

}

 

-(void) dealloc

{

[superdealloc];

}

 

-(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

 

@interfaceSneakyButtonSkinnedBase (Extension)

+(id) skinnedButton;

@end

#import "SneakyExtensions.h"

 

 

@implementation SneakyButton(Extension)

+(id) button

{

return [[[SneakyButtonalloc]initWithRect:CGRectZero]autorelease];

}

 

+(id) buttonWithRect:(CGRect)rect

{

return [[[SneakyButtonalloc]initWithRect:rect]autorelease];

}

@end

 

 

@implementation SneakyButtonSkinnedBase(Extension)

+(id) skinnedButton

{

return [[[SneakyButtonSkinnedBasealloc]init]autorelease];

}

@end

我导入了所有.h文件,因为在这个类别中,我打算对每个SneakyInput都进行扩展。

用于SneakyButton的initWithRect方法的CGRect参数其实并没有用到,所以我们可以用button方法来替代SneakyButton的初始化方法:

fireButton=[SneakyButton button];

 

现在开始订制SneakyButton的外观。首先制作4张100*100大小的图片,分别表示按钮的4个状态:默认,按下,激活,失效。默认状态即按钮未被按下时的外观,于此相反的是按下状态。激活状态仅发生在切换按钮的时候,此时按钮被激活,或获得焦点。失效状态表示按钮此时是无效的。例如,当武器过热时,你会有几秒钟无法射击,此时应该让按钮失效并让按钮显示失效状态的图片。当然,在这里,我们仅需要使用默认图片和按下图片。

修改InputLayer的addFireButton 方法为:

 

-(void) addFireButton

{

float buttonRadius =50;

CGSize screenSize = [[CCDirectorsharedDirector]winSize];

 

fireButton = [SneakyButtonbutton];

fireButton.isHoldable =YES;

SneakyButtonSkinnedBase* skinFireButton = [SneakyButtonSkinnedBaseskinnedButton];

skinFireButton.position =CGPointMake(screenSize.width - buttonRadius, buttonRadius);

skinFireButton.defaultSprite = [CCSpritespriteWithSpriteFrameName:@"button-default.png"];

skinFireButton.pressSprite = [CCSpritespriteWithSpriteFrameName:@"button-pressed.png"];

skinFireButton.button =fireButton;

[selfaddChild:skinFireButton];

}

 

这里设置了isHoldable属性,这意味着当你按下按钮不放时会导致子弹不停地发射。现在,不需要设置radius属性,因为接下来的SneakyButtonSkinnedBase中的图片的大小就决定了radius的值。SneakyButtonSkinedBase的静态初始化方法skinnedButton是我们在Extension这个类别中定义过的。

现在,我们用SneakyButtonSkinnedBase替代了SneakyButton,用设置SneakyButtonSkinnedBase的位置替代了设置SneakyButton的位置。并且设置了SneakyButtonSkinnedBase的状态图片。

注意最后两句代码, SneakyButtonSkinnedBase的button属性持有了SneakyButton对象引用,这样fireButton对象隐式地被加到了InputLayer。

 

update方法也修改了,这次调用了GameScene的射击方法:

-(void) update:(ccTime)delta

{

totalTime += delta;

if (fireButton.active &&totalTime > nextShotTime)

{

nextShotTime = totalTime + 0.5f;

GameScene* game = [GameScenesharedGameScene];

[game shootBulletFromShip:[gamedefaultShip]];

}

//Allow faster shooting by quickly tapping the fire button.

if (fireButton.active ==NO)

{

nextShotTime = 0;

}

}

变量totalTime和nextShortTime限制了子弹射击的速度为2发/秒。如果发射按钮的active状态为NO(意味着它未被按下),nextshortTime变量被设置为0,从而保证你下一次按下发射键时,子弹不再判断时间,直接发射。快速点击发射键导致子弹的射速会更快(超过连续发射)。

 

5、动作控制

我们需要使用SneakyJoystick来生成一个虚拟摇杆。 首先,增加一个SneakyJoystick成员变量:SneakyJoystick*joystick;

增加一个addJoystick方法,这次我们直接使用了SneakyJoystickSkinnedBase,以定制其外观,:

-(void) addJoystick

{

float stickRadius =50;

 

joystick = [SneakyJoystickjoystickWithRect:CGRectMake(0,0, stickRadius,stickRadius)];

joystick.autoCenter =YES;

joystick.hasDeadzone =YES;

joystick.deadRadius =10;

SneakyJoystickSkinnedBase* skinStick = [SneakyJoystickSkinnedBaseskinnedJoystick];

skinStick.position =CGPointMake(stickRadius *1.5f, stickRadius *1.5f);

skinStick.backgroundSprite = [CCSpritespriteWithSpriteFrameName:@"button-disabled.png"];

skinStick.backgroundSprite.color =ccMAGENTA;

skinStick.thumbSprite = [CCSpritespriteWithSpriteFrameName:@"button-disabled.png"];

skinStick.thumbSprite.scale =0.5f;

skinStick.joystick =joystick;

[selfaddChild:skinStick];

}

 

同样的,我们在extension类别中为SneakyJoystickSkinnedBase增加了新的静态方法skinnedJoystick:

@implementationSneakyJoystickSkinnedBase (Extension)

+(id) skinnedJoystick

{

return [[[SneakyJoystickSkinnedBasealloc]init]autorelease];

}

@end

SneakyJoystick的初始化方法需要一个CGRect参数,与SneakyButton不同,这里CGRect的确能决定摇杆的半径。autoCenter设置为YES可以使摇杆自动回到中心位置。hasDeadZone和deadRadius属性决定了你能移动的最小半径,在此范围内的移动视作无效。如果hasDeadZone=NO,你几乎不可能让摇杆稳定保持在中心位置。

摇杆与屏幕边缘稍微空出了一些距离,对于游戏而言摇杆的位置和尺寸不是最恰当的,但用来演示足够了。

如果摇杆过于靠近屏幕边缘,手指很容易移出屏幕从而失去对飞船的控制。

我决定让摇杆使用button-disabled.png作为背景图,同时摇杆大小缩放为原来的一半。这里backgroundSprite和thumbSprite使用的图片都是同一张。二者的区别是:

摇杆的手柄(thumbSprite)半径仅为按钮背景(backgroundSprite)半径的一半。button-disabled.png图片是一个灰色的圆形按钮。这样的将导致虚拟摇杆由两个灰色的正圆构成,在一个圆形的中心还有一个一半大小的小圆。

而且,把背景图选取为灰色图片是特意的。因为backgroundSprite的color属性被设置为品红,于是backgroundSprite的灰色图片被着色为品红了!通过把color属性设置为不同的颜色:红色、绿色、黄色,你可以轻易地为backgroundSprite染上不同的颜色!

当然,控制飞船移动的代码是在update方法中:

 

GameScene* game = [GameScenesharedGameScene];

Ship* ship = [gamedefaultShip];

CGPoint velocity =ccpMult(joystick.velocity,200);

if (velocity.x !=0 && velocity.y !=0) {

ship.position = CGPointMake(ship.position.x + velocity.x * delta, ship.position.y + velocity.y * delta);

}

我们在GameScene中增加了defaultShip方法,以便在这里访问ship对象。摇杆的velocity属性用于改变飞船的位置,但需要根据比例放大,这使得摇柄能在控制上能够有一个放大效果。放大比例是一个经验值,在游戏中感觉可以就行了。

万一出现update方法调用不规律的情况,为确保飞船平滑移动的效果,我们必须利用update方法的delta参数。Delta参数传递了从上次update调用以来到本次调用之间的时间值。另外,飞船可能被移出屏幕区域外——你肯定不希望这样。你可能想把代码直接加在InputLayer中ship位置被改变的地方。这会有一个问题:你是为了防止摇柄把飞船移到屏幕外?还是为了让飞船根本就没有移到屏幕外的能力?无疑,后者更为优雅——这样,你就要覆盖Ship类的setPosition方法了:

-(void) setPosition:(CGPoint)pos

{

CGSize screenSize = [[CCDirectorsharedDirector]winSize];

float halfWidth =contentSize_.width *0.5f;

float halfHeight =contentSize_.height *0.5f;

 

//防止飞船移出屏幕

if (pos.x < halfWidth)

{

pos.x = halfWidth;

}

elseif (pos.x > (screenSize.width - halfWidth))

{

pos.x = screenSize.width - halfWidth;

}

if (pos.y < halfHeight)

{

pos.y = halfHeight;

}

elseif (pos.y > (screenSize.height - halfHeight))

{

pos.y = screenSize.height - halfHeight;

}

//一定要调用父类的同名方法

[supersetPosition:pos];

}

 

每当飞船的位置发生改变,上面的代码会对飞船的位置进行一个边界检测。如果飞船x,y坐标移出了屏幕外,它将被保持在屏幕边沿以内。

由于position是属性,下面语句会调用setPosition方法:

ship.position=CGPointMake(200,100);

点语法比发送getter/setter消息更简短,当然我们也可以用发送消息的语法:

[shipsetPosition:CGPointMake(200,100)];

通过这种方法,你可以重写其他基类的方法,以改变游戏对象的行为。例如,如果要限定一个对象只能旋转0-180度,你可以重写setRotation(float)rotation方法在其中加入限制旋转的代码。

 

6、数字控制

如果你的游戏不适合采用模拟控制,你可以把SneakyJoystick类转换成数字控制,即 D-pad。这需要改动的代码很少:

joystick=[SneakyJoystick joystickWithRect:CGRectMake(0,0,stickRadius,stickRadius)];

 

joystick.autoCenter=YES;

 

// 减少控制方向为8方向

joystick.isDPad=YES;

joystick.numberOfDirections=8;

 

dead zone属性被删除了——在数字控制中他们不再需要了。isDPad属性设置为YES,表明采用数字控制。同时你可以定义方向数。如果你想让D-pads在上下左右4个方向的同时,增加斜角方向(同时按下两个方向将使角色沿斜角移动),你只需要把numberOfDirections设置为8。SneakyJoystick自动把模拟控制的方向转换成8个方向。当然,如果你把方向数设置成6,你会得到一些怪异的结果。

 

7、GP Joystick

SneakyInput不是仅有的解决方案。还有GP Joystick,一个付费的商业产品,不过费用很低:

http://wrensation.com/?p=36

 

如果你想知道GPJoystick和SneakyInput有什么区别,你可以观看GP Joystick的YouTube视频:

http://www.youtube.com/user/SDKTutor

在这里也提供了几个cocos2d的视频教程。

 

三、结论

这章你学习了背景平行视差滚动效果:背景无限循环滚动(去除抖动),如何将背景拆分成不同的图层以便Zwoptex能去掉透明区域,同时让这些图片保持正确的位置。

 

接下来是指定屏幕分辨率。假设你想创建一个iPad的版本,除了必需创建1024*768的图片外,你可以使用相同技术。这个工作你可以自己尝试一下。

后半章介绍了SneakyInput,一个开源项目,可以在cocos2d游戏中加入虚拟摇杆和按钮。它并不是最好的,但对大多数游戏来说已将足够,无论如何,总胜过你自己去写虚拟摇杆的代码。

现在,飞船已经能控制了并且不再能飞出屏幕边缘了。通过按下发射按钮,它也能进行射击了。但这个游戏仍然还有许多东西要做。如果没有什么东西给你射击,那么射击游戏就不能成为射击游戏了。下一章继续。


========================================再割=========================================================


参考:http://www.weimobile.com/archiver/showtopic-71.aspx


ocos2d漫游指南第十二章海阔天空原文在此:
http://www.raywenderlich.com/3857/how-to-create-dynamic-textures-with-ccrendertexture

示例代码在此:
http://www.raywenderlich.com/downloads/TinySeal1.zip
  
经过前面十一章的学习,给游戏添加几个制作好的背景图片看来已经难不倒你了。但是假如,万一,如果你想要动态的创建游戏背景,并可以修改背景的颜色,渐变和效果呢?

如果你玩过独立开发者Andreas Illiger设计的Tiny Wings,就会发现它的背景其实是动态生成的。








在本系列教程(共有3章)中,我们会学习以下内容:
1.如何动态创建纹理
2.如何使用Gimp来创建无缝对接的纹理图
3.如何将阴影和高亮效果混合到纹理当中,以产生更真实的效果
4.如何创建条纹状纹理
5.如何设置纹理贴图,使之重复出现
6.更多

这里我们假定你已经具备了cocos2d的基础知识,否则请从第一章开始看起吧。

如何使用CCRenderTexture创建动态纹理

Tiny Wings 这款游戏中有个很酷的特点,里面的纹理贴图每天都会变化,从截图中我们可以看到这一点:







这个看起来实在是太酷了!但是,究竟是如何实现的呢?很幸运的是,我们有一个很酷的类,CCRenderTexture,使用这个类,我们可以轻松的绘制纹理图,并在游戏中重复使用。

对CCRenderTexture的使用并不复杂,你只需要按以下5个步骤进行就好:
1.创建新的CCRenderTexture。
你需要指定纹理图的高度和宽度。
2.调用CCRenderTexture:begin
使用这种方式,可以设置OpenGL,从而将需要绘制的图绘制到CCRenderTexture之中(不是直接绘制到屏幕中)
3.绘制到纹理图中。
你可以直接使用原始的OpenGL命令进行绘制,也可以调用访问现有cocos2d对象的方法来绘制(使用此类方法来调用所需的OpenGL命令来绘制这些对象)。
4.调用CCRenderTexture:end
使用这种方式,将会渲染纹理图,并关闭绘制到纹理图。
5.从纹理图中创建一个新的精灵
到了这一步,你就可以使用CCRenderTexture的sprite.texture属性来创建新的精灵。

请注意,你可以使用1-3步来添加和修改纹理,这一点对于绘图类应用会非常方便。当然在这个教程中,我们只需要绘制一次。

有了整体的概念后,让我们实际开工吧!

首先创建一个新的项目,选择cocos2dbox2d模板。尽管这一章还暂时用不到box2d,但接下来的一章需要用到,所以我们还是提前准备好吧。

把新创建的项目命名为TinySeal,并保存在自己习惯的位置。

打开HelloWorldLayer.h,然后用以下代码替代其中的内容:
#import "cocos2d.h"

@interface HelloWorldLayer : CCLayer
{
        CCSprite * _background;
}

+(CCScene *) scene;

@end

以上代码只是添加了一个实例变量,可以跟踪我们要创建的动态背景:

接下来,让我们切换到HelloWorldLayer.mm文件,并使用以下代码来替代其中的内容(删除了原有的Box2d内容,从而得到一个空的屏幕):

#import "HelloWorldLayer.h"

@implementation HelloWorldLayer

+(CCScene *) scene {
        CCScene *scene =[CCScene node];
        HelloWorldLayer *layer =[HelloWorldLayer node];
        [scene addChild: layer];
        return scene;
}

-(id) init {
        if((self=[super init])){
        }
        return self;
}
@end

如果这时候编译运行,你会看到一片空白。
现在让我们在init方法的上面添加以下方法:

-(CCSprite *)spriteWithColor:(ccColor4F)bgColortextureSize:(float)textureSize {

// 1: Create new CCRenderTexture
   CCRenderTexture *rt =[CCRenderTexturerenderTextureWithWidth:textureSize height:textureSize];

// 2: Call CCRenderTexture:begin
[rt beginWithClear:bgColor.r g:bgColor.g b:bgColor.ba:bgColor.a];

// 3: Draw into the texture
// We'll add this later

// 4: Call CCRenderTexture:end
[rt end];

// 5: Create a new Sprite from the texture
return[CCSprite spriteWithTexture:rt.sprite.texture];

}

你看,我们的五个步骤清清楚楚明明白白真真切切。
有一点要注意的是,在第2步中,我们没有使用老的CCRenderTexture:begin方法,而是用了beginWithClear:g:b:a:方法在绘制之前创建一个带特定颜色的纹理图。

到目前为止,我们什么也没画,只是一个纯色纹理图而已。
让我们用下面的代码替代init方法,同时添加如下几个新方法:

-(ccColor4F)randomBrightColor {

while(true){
float requiredBrightness =192;
       ccColor4B randomColor =
           ccc4(arc4random()%5,
                arc4random()%5,
                arc4random()%5,
255);
if(randomColor.r > requiredBrightness ||
           randomColor.g > requiredBrightness ||
           randomColor.b > requiredBrightness){
return ccc4FFromccc4B(randomColor);
}
}

}

-(void)genBackground {

[_background removeFromParentAndCleanup:YES];

    ccColor4FbgColor =[self randomBrightColor];
   _background =[self spriteWithColor:bgColor textureSize:512];

    CGSizewinSize =[CCDirector sharedDirector].winSize;
   _background.position = ccp(winSize.width/2,winSize.height/2);      
[self addChild:_background z:-1];

}

-(id) init {
if((self=[superinit])){              
[self genBackground];
       self.isTouchEnabled=YES;      
}
return self;
}

-(void)ccTouchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{

[self genBackground];

}

在上面的代码中,randomBrightColor方法是一个辅助性方法,用于创建一个随机颜色。需要注意的是它使用ccc4B(通过指定0-255范围内的R/G/B/A数值来确定颜色),并确保至少有一个>192,这样我们就不会得到过暗的颜色。然后我们使用ccc4FFromccc4B方法把随机颜色转换成ccc4F(R/G/B/A的值在0-1的范围内)。
genBackground方法会调用我们之前的spriteWithColor方法生成背景精灵,并将其添加到屏幕的中间。
至于init方法,它调用了genBackground方法,并允许支持触碰事件,这样让我们触碰屏幕时就会随机生成其它颜色的背景!








为纹理图添加噪点

正如你在Tiny Wings这款游戏中所看到的,纹理图不仅仅是纯色,我们需要添加一些噪点,让它看上去有阴影和高亮效果。
如果你是技术狂人,当然可以自己来写一堆代码生成噪点。不过哥一向是喜欢偷懒的人,所以就提前制作了一些噪点直接添加进项目。
要制作随机噪点很容易,我们有一款免费的图像编辑程序,Gimp(http://gimp.lisanet.de/Website/Download.html)。接下来的部分内容会教你如何制作纹理图噪点。但如果你看了下面的这哥们不后退(怎么看都有姚明的风范,难道博主你是潜伏已久的姚黑?),那么可以直接下载哥为你准备的噪点图(),添加到项目里,并坚决跳过下面的这一部分:)。






当然,如果你是个热爱学习的笨小孩,喜欢接触新鲜事物,也不妨跟着哥一步步来制作吧(译注:我曾一直迷惑为何国外的这些技术牛人很少用photoshop或adobe的各种软件,用ps做这个多简单呀,后来才知道ps太贵,我天朝子民可以免费享用,但作为独立开发者的老外们嫌太贵,且恪守知识产权,从不敢越雷池一步,让我等惭愧不已。。。):

打开Gimp软件,点击File\New,创建一个大小为512*512的新图像,然后点击Filters\Render\Clouds\SolidNoise,根据你的需要可以调整参数,然后点击Ok,你会看到如下的图片:









我们打算用这个噪点图来乘以纹理图的色彩。这样在白色显示区域,原始的纹理图色彩会通过,而在黑色显示区域,原始纹理图色彩则会变暗。

照目前来看,上面这个噪点图的黑色部分太多,可能会影响最终的效果。所以让我们点击Colors\Levels,把”OutputLevels”的滑动条拖到右边,如图所示(好吧,你要说在PS里面调这个太简单了。。。让我们直接跳过并无视吧):








点击OK。现在只剩最后一步,噪点纹理图应该看起来是天衣无缝的。

让我们点击Filters\Map\Make Seamless,搞定了!

使用File\SaveAs将图片保存为Noise.png。找到这个文件,并把它拖到项目的Resources中,确保选中”Copy items intodestination group’s folder”,点击Finish。

恭喜你,现在我们准备好了一个噪点纹理,可以让动态纹理看起来更酷,更真实!

当然,你还可以使用Gimp中的滤镜来轻松创造出更多效果!(天朝的攻城师们,你们可以随便使用PS这么伟大的创意工具,请鄙视一下可怜的博主吧!)

将噪点应用到纹理

在spriteWithColor:textureSize方法中的第3步添加以下代码:

CCSprite *noise =[CCSprite spriteWithFile:@"Noise.png"];
[noise setBlendFunc:(ccBlendFunc){GL_DST_COLOR, GL_ZERO}];
noise.position = ccp(textureSize/2, textureSize/2);
[noise visit];

以上代码使用噪点纹理创建了一个CCSprite,将其放置在渲染纹理的中心位置,然后调用visit方法,该方法会执行所需的OpenGL命令来绘制纹理。
这几条代码很清晰,不过似乎setBlendFunc这个方法看起来有点恼火。这个看起来很怪的方法究竟是干神马的?

第一个传递进来的常量(GL_DST_COLOR)说明了应该如何使用乘法计算导入色彩/源色彩(噪点纹理),而第二个传递进来的常量(GL_ZERO)则说明了该如何使用乘法计算现有色彩/目标色彩(色彩纹理)

接下来是具体的计算方法:
1.现有色彩会乘以GL_ZERO,也就是说现有色彩会被清除。
2.导入色彩(噪点纹理色彩)会乘以GL_DST_COLOR,而GL_DST_COLOR其实就是现有色彩,也就是说我们会使用噪点纹理色彩乘以现有色彩。这样噪点图中越“白”,现有色彩就会显得更亮,反之则显得更暗。
3.上述两种色彩会添加在一起,而鉴于第1步的时候现有色彩已归零,那么实际上起作用的是第2步的结果。

好吧,哥虽然费了这么多口水给大家讲OpenGL具体的计算过程,其实---哥也不是完全看懂了的。幸运的是,我们可以用一个在线工具(http://www.andersriggelsen.dk/OpenGL/)视觉化的呈现出最终的效果!


总之,现在你来编译运行项目,可以看到在纹理图上有一些阴影效果了。不过,请注意这些效果在模拟器上看起来不是很爽,最好在设备上实际测试吧!








给纹理添加渐变效果

为了让纹理看起来更舒服一点,我们可以添加一个从顶部到底部的渐变效果,这样纹理色彩从上到下会越来越暗。

你当然可以直接在Gimp或者PS里面事先修改后,但我们这里还是用代码来事先,这样所生成的效果更加动态,也便于后续的修改。

怎么实现呢?让我们设想在纹理的顶部绘制一个黑色的矩形,但这个矩形在顶部是完全透明的,而在底部则完全不透明。这样做的话顶部色彩没有任何变化,而越到下面图像会越来越暗。

为了实现这一点,我们需要用到一些基本的OpenGL命令。好吧,哥听到你脑袋中的轰鸣声了。不要恐慌,哥会详细解释这些命令的作用,让你有个基本的印象。在这部分教程的结尾,还会给你一个链接,用于获取更多的参考信息。

在spriteWithColor:textureSize方法中,在创建噪点精灵之前添加以下代码:


glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

float gradientAlpha=0.7;  
CGPoint vertices[4];
ccColor4F colors[4];
int nVertices =0;

vertices[nVertices]= CGPointMake(0, 0);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};
vertices[nVertices]= CGPointMake(textureSize, 0);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};
vertices[nVertices]= CGPointMake(0, textureSize);
colors[nVertices++]=(ccColor4F){0, 0, 0, gradientAlpha};
vertices[nVertices]= CGPointMake(textureSize, textureSize);
colors[nVertices++]=(ccColor4F){0, 0, 0, gradientAlpha};

glVertexPointer(2, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);

glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);


以上代码中,我们做的第一件事情就是禁用了GL_TEXTURE_2D 和GL_TEXTURE_COORD_ARRAY OpenGLstate,因为我们需要绘制的是色彩而非纹理。

在绘制纹理的时候,我们注意到左上角是(0,0),这个和cocos2d中左下角为(0,0)是不同的。
那么接下来的8行代码其实就是定义纹理的四个几何定点的信息-左上,右上,左下,右下-然后是各个点的色彩信息。
你可能在想为何我们按照这种顺序绘制顶点,这是因为我们打算绘制两个三角形来拼成这个矩形:






我们会使用GL_TRIANGLE_STRIP来绘制三角形,也就是说第一个三角形是数组中的前三个顶点,而另一个三角形则用到了上个三角形的两个顶点,以及另外一个顶点。

这样第一个三角形由V0,V1,V2构成,而第二个三角形由V1,V2,V3构成。

在定义了顶点坐标和色彩的数组后,我们使用glVertexPointer(每个顶点坐标由两个浮点数表示)和glColorPointer(每个色彩值使用四个浮点数来标示)将这些信息传递到OpenGL,并调用glDrawArrays,并指定使用GL_TRIANGLE_STRIP进行绘制。

在代码的最后,我们重新启用了GL_TEXTURE_COORD_ARRAY 和GL_TEXTURE_2D,从而让一切回到最初的状态。








使用条纹来创建纹理

在我们开始写相关的代码之前,不妨先想想该怎么做。

首先让我们给纹理添加一种色彩(比如蓝色),然后让我们绘制几个对角线形状的条纹(比如绿色),如下图所示:







需要注意的是,既然这些条纹是沿着对角线的,我们实际上是从纹理边界之外开始绘制,并继续绘制到纹理边界之外。

还需要注意的是,为了获得漂亮的45度角,从V1到V0的偏移值应该是textureSize。

概念清楚了,现在我们进入代码部分。在init方法的前面添加以下方法:

-(CCSprite *)stripedSpriteWithColor1:(ccColor4F)c1color2:(ccColor4F)c2textureSize:(float)textureSize stripes:(int)nStripes {

// 1: Create new CCRenderTexture
   CCRenderTexture *rt =[CCRenderTexturerenderTextureWithWidth:textureSize height:textureSize];

// 2: Call CCRenderTexture:begin
[rt beginWithClear:c1.r g:c1.g b:c1.b a:c1.a];

// 3: Draw into thetexture  

// Layer 1: Stripes
   glDisable(GL_TEXTURE_2D);
   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   glDisableClientState(GL_COLOR_ARRAY);

    CGPointvertices[nStripes*6];
int nVertices =0;
float x1 =-textureSize;
float x2;
float y1 = textureSize;
float y2 =0;
float dx = textureSize / nStripes *2;
float stripeWidth = dx/2;
for(int i=0; i<nStripes; i++){
       x2 = x1 + textureSize;
       vertices[nVertices++]= CGPointMake(x1, y1);
       vertices[nVertices++]= CGPointMake(x1+stripeWidth, y1);
       vertices[nVertices++]= CGPointMake(x2, y2);
       vertices[nVertices++]= vertices[nVertices-2];
       vertices[nVertices++]= vertices[nVertices-2];
       vertices[nVertices++]= CGPointMake(x2+stripeWidth, y2);
       x1 += dx;
}

   glColor4f(c2.r, c2.g, c2.b, c2.a);
   glVertexPointer(2, GL_FLOAT, 0, vertices);
   glDrawArrays(GL_TRIANGLES, 0, (GLsizei)nVertices);

   glEnableClientState(GL_COLOR_ARRAY);
   glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   glEnable(GL_TEXTURE_2D);

// Layer 2:Noise  
    CCSprite*noise =[CCSprite spriteWithFile:@"Noise.png"];
[noise setBlendFunc:(ccBlendFunc){GL_DST_COLOR, GL_ZERO}];
   noise.position = ccp(textureSize/2, textureSize/2);
[noise visit];

// 4: Call CCRenderTexture:end
[rt end];h

// 5: Create a new Sprite from the texture
return[CCSprite spriteWithTexture:rt.sprite.texture];

}

以上代码中的大部分都是用于创建CCRenderTexture的,而创建条纹的部分(layer1)是新的,让我们大概解释下。
首先它创建了一个顶点数组,每个条纹都需要6个顶点(每个三角形需要3个订单,两个三角形就是6个顶点)。这里没有使用三角形条纹,因为条纹之间是不相邻的。

第一个顶点的位置在(-textureSize,textureSize)处,而下一个顶点则在(-textureSize+stripWidth,textureSize)。第三个顶点的位置是(0,0),第四个顶点是(stripWidth,textureSize)。每次绘制下一个条纹的时候,我们向前条纹宽度乘以2,直到完成所有的条纹。

现在考虑修改genBackground方法如下:
-(void)genBackground {

[_background removeFromParentAndCleanup:YES];

    ccColor4FbgColor =[self randomBrightColor];
    ccColor4Fcolor2 =[self randomBrightColor];
//_background = [self spriteWithColor:bgColortextureSize:512];
int nStripes =((arc4random()%4)+1)*2;
   _background =[self stripedSpriteWithColor1:bgColor color2:color2textureSize:512 stripes:nStripes];

   self.scale =0.5;

    CGSizewinSize =[CCDirector sharedDirector].winSize;
   _background.position = ccp(winSize.width/2,winSize.height/2);      
[self addChild:_background];

}


这里我们调用了新的条纹纹理生成方法,并将层缩放到0.5倍,以便看到完整的512*512纹理图。

编译运行,当你触碰屏幕的时候,可以随机生成不同条纹形状的纹理。







让背景重复出现

无论是条纹状的背景,还是渐变的背景,我们都需要把它们平铺在一个大的空间,而这个空间要比纹理本身更大。

如果想偷懒,你可以整一堆CCSprites出来,然后把它们拼在一起。天啊,这个想法实在太疯狂了!我们可以用代码来设置纹理,让它们自动重复出现!

在HelloWorldLayer.m中对代码做出以下修改:

// 添加到 genBackground方法中,在调用addChild方法的前面
ccTexParams tp ={GL_LINEAR, GL_LINEAR, GL_REPEAT,GL_REPEAT};
[_background.texture setTexParameters:&tp];

// Add to bottom of init
[self scheduleUpdate];

// 在init方法的后面添加以下方法:
-(void)update:(ccTime)dt {

float PIXELS_PER_SECOND =100;
staticfloat offset =0;
    offset +=PIXELS_PER_SECOND * dt;

    CGSizetextureSize = _background.textureRect.size;
[_background setTextureRect:CGRectMake(offset, 0,textureSize.width, textureSize.height)];

}

稍微解释一下两个重要的纹理参数:

1.GL_LINEAR的意思是:在以小于或大于原始大小的尺寸显示纹理时,要考虑临近像素的大小进行线性运算。
2.GL_REPEAT的意思是:如果要在纹理图的边界外显示纹理,则假定纹理图会连续平铺。

最后,我们使用定时器使得纹理的可见部分沿着x轴不断延伸。这样你就可以随着时间的推移看到纹理重复出现。

编译运行项目,现在纹理背景可以连续滚动和重复出现!你也可以对渐变背景图进行同样的尝试。

再来点高亮效果

如果你仔细观察TinyWings游戏里面山丘的纹理,会发现在山丘的顶部有一点高亮效果,而在底部则有渐变,这样看上去更舒服一些。让我们也学着试试看吧:

在stripedSpriteWithColor1:方法中,在调用glDrawArrays命令的后面添加以下代码:

// layer 2: gradient
glEnableClientState(GL_COLOR_ARRAY);

float gradientAlpha=0.7;  
ccColor4F colors[4];
nVertices =0;

vertices[nVertices]= CGPointMake(0, 0);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};
vertices[nVertices]= CGPointMake(textureSize, 0);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};
vertices[nVertices]= CGPointMake(0, textureSize);
colors[nVertices++]=(ccColor4F){0, 0, 0, gradientAlpha};
vertices[nVertices]= CGPointMake(textureSize, textureSize);
colors[nVertices++]=(ccColor4F){0, 0, 0, gradientAlpha};

glVertexPointer(2, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);

// layer 3: tophighlight  
float borderWidth = textureSize/16;
float borderAlpha = 0.3f;
nVertices =0;

vertices[nVertices]= CGPointMake(0, 0);
colors[nVertices++]=(ccColor4F){1, 1, 1, borderAlpha};
vertices[nVertices]= CGPointMake(textureSize, 0);
colors[nVertices++]=(ccColor4F){1, 1, 1, borderAlpha};

vertices[nVertices]= CGPointMake(0, borderWidth);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};
vertices[nVertices]= CGPointMake(textureSize, borderWidth);
colors[nVertices++]=(ccColor4F){0, 0, 0, 0};

glVertexPointer(2, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);


上面的代码中,layer2部分创建了一个渐变效果,和我们之前所作的类似,而第二部分则给条纹的顶部添加了一点高亮效果,看起来像阳光照射在上面的感觉一样。

编译运行,现在你的条纹看起来更舒服了,既有渐变,又有高亮效果。








从下一章开始,我们将以本章的动态纹理为基础,创建一个类似Tiny Wings的游戏!

当然,为了彻底看懂本部分的内容,或许你还需要再去了解一点OpenGL ES的知识!

转自:
http://blog.sina.com.cn/s/blog_4b55f6860100spob.html

原创粉丝点击