天天看点

疯狂ios讲义之疯狂打飞机(2)

下面添加游戏背景图片和玩家操控的飞机。打开HelloWorldLayer.m文件,首先定义4个变量,实现代码如下。

程序清单:codes/13/13.14/AirfightGame/AirfightGame/HelloWorldLayer.m

1

2

3

4

5

6

<code>// 精灵表单tag</code>

<code>    </code><code>static</code> <code>NSInteger kTagBatchNode = 1;</code>

<code>    </code><code>// 玩家飞机变量</code>

<code>    </code><code>CCSprite* planeSprite;</code>

<code>    </code><code>// 屏幕宽度、高度的变量</code>

<code>    </code><code>NSInteger screenWidth , screenHeight;</code>

然后在init方法中添加背景图片,实现代码如下(程序清单同上)。

7

8

9

10

11

12

13

14

15

16

17

18

19

<code>-(id) init</code>

<code>{</code>

<code>    </code><code>if</code><code>( (self=[super init]) ) {</code>

<code>        </code><code>// ①本例使用精灵表单来优化游戏性能,</code>

<code>        </code><code>// CCSpriteBatchNode中的所有CCSprite只会被渲染1次,因此可以提高游戏的FPS</code>

<code>        </code><code>batchNode = [CCSpriteBatchNode batchNodeWithFile:@</code><code>"airfightSheet.png"</code><code>];</code>

<code>        </code><code>batchNode.position = CGPointZero;</code>

<code>        </code><code>[self addChild:batchNode z:0 tag:kTagBatchNode];</code>

<code>        </code><code>// 获取屏幕宽度和高度</code>

<code>        </code><code>CGSize winSize = [[CCDirector sharedDirector] winSize];</code>

<code>        </code><code>screenWidth = winSize.width;</code>

<code>        </code><code>screenHeight = winSize.height;</code>

<code>        </code><code>// ②添加背景图片</code>

<code>        </code><code>CCSprite* bgSprite = [CCSprite spriteWithSpriteFrameName:@</code><code>"bg1.png"</code><code>];</code>

<code>        </code><code>bgSprite.position = ccp(screenWidth/2,screenHeight/2);</code>

<code>        </code><code>[batchNode addChild:bgSprite];</code>

<code>    </code><code>}</code>

<code>    </code><code>return</code> <code>self;</code>

<code>}</code>

以上代码①创建了一个CCSpriteBatchNode使用精灵表单加载精灵,优化游戏性能。接下来获取屏幕宽度和高度。代码②初始化了一个背景图片精灵,添加到CCSpriteBatchNode当中。

最后在onEnter方法中添加玩家飞机,实现代码如下(程序清单同上)。

<code>// 节点调用init方法以后将会调用此方法</code>

<code>-(</code><code>void</code><code>) onEnter{</code>

<code>    </code><code>[super onEnter];</code>

<code>    </code><code>// ③添加玩家飞机精灵</code>

<code>    </code><code>planeSprite = [CCSprite spriteWithSpriteFrameName:@</code><code>"plane0.png"</code><code>];</code>

<code>    </code><code>planeSprite.position = ccp(screenWidth/2, planeSprite.contentSize.height/2+5);</code>

<code>    </code><code>[batchNode addChild:planeSprite];</code>

代码③初始化了一个玩家飞机精灵,通过屏幕的宽度和高度设置相对坐标,添加到CCSpriteBatchNode当中。

疯狂ios讲义之疯狂打飞机(2)

再次编译并运行游戏,资源加载完毕后将显示背景图片和玩家飞机。模拟器显示效果如图13.61所示。

疯狂ios讲义之疯狂打飞机(2)

现在,玩家飞机精灵已经显示在屏幕当中了,但是这只是一个静态效果,我们可以使用动画技术,让玩家飞机精灵实现飞行动画效果,这样可以使得游戏效果更加逼真。

打开HelloWorldLayer.m文件,在私有分类中添加一个新的辅助方法,因为之后很多地方都需要获取动画帧,所以将获取动画帧的代码封装起来,从而达到代码重用的效果。示例代码如下:

<code>-(CCAnimation*) getAnimationByName:(NSString*)animName delay:(</code><code>float</code><code>) delay animNum:(</code><code>int</code><code>) num;</code>

该方法的作用是根据动画帧的名字和动画帧的数量,以及动画帧与帧之间的间隔时间创建一个CCAnimation动画。使用该方法要注意如下两点。

q动画帧的命名必须带序号,比如xxx1.png、xxx2.png等。

q动画帧的命名必须连续,而且必须从0开始命名。

接下来是该方法的具体实现,实现代码如下(程序清单同上)。

<code>-(CCAnimation*)getAnimationByName:(NSString *)animName delay:(</code><code>float</code><code>)delay animNum: (</code><code>int</code><code>)num{</code>

<code>    </code><code>NSMutableArray *animeFrames = [NSMutableArray arrayWithCapacity:num];</code>

<code>    </code><code>for</code> <code>(</code><code>int</code> <code>i=0; i&lt; num; i++) {</code>

<code>        </code><code>// 获取动画图片名称</code>

<code>        </code><code>NSString *frameName = [NSString stringWithFormat:@</code><code>"%@%d.png"</code><code>,animName,i];</code>

<code>        </code><code>// 根据图片名称获取动画帧</code>

<code>        </code><code>CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache]</code>

<code>            </code><code>spriteFrameByName:frameName];</code>

<code>        </code><code>[animeFrames addObject:frame];</code>

<code>    </code><code>return</code> <code>[CCAnimation animationWithSpriteFrames:animeFrames delay:delay];</code>

回到onEnter方法中,在③添加玩家飞机精灵部分代码之后添加动画效果,实现代码如下(程序清单同上)。

<code>// ④玩家飞机动画(尾部喷火)</code>

<code>CCAnimation* planeFlyAnimation = [self getAnimationByName:@</code><code>"plane"</code> <code>delay:0.08 animNum:2];</code>

<code>// 重复动作</code>

<code>id planeFlyAction = [CCRepeatForever actionWithAction:</code>

<code>    </code><code>[CCAnimate actionWithAnimation:planeFlyAnimation]];</code>

<code>// 执行动作,达到飞机尾部喷火效果</code>

<code>[planeSprite runAction:planeFlyAction];</code>

这段代码调用辅助方法getAnimationByName: delay: animNum:创建了玩家飞机飞行动画,然后使用CCRepeatForever和CCAnimation创建了一个重复飞行动作,最后调用planeSprite的runAction方法播放动画效果。再次编译并运行游戏,屏幕上玩家飞机的尾部会产生不断的喷火效果,给玩家的感觉就是飞机在不断地向前飞行。

现在,我们要完成控制玩家飞机的移动了。找到onEnter方法,在④玩家飞机动画部分代码后添加touch事件,实现代码如下(程序清单同上)。

<code>// ⑤这是一种新的方式来激活层的touch事件</code>

<code>[[[CCDirector sharedDirector] touchDispatcher]</code>

<code>    </code><code>addTargetedDelegate:self priority:0 swallowsTouches:YES];</code>

这是一种新的方式来激活层的touch事件,老的方式是设置层的isTouchEnabled属性为“YES”。cocos2d中CCLayer默认是采用addTargetedDelegate: priority: swallowsTouches:这种方式进行touch事件处理的。每次touch事件发生时,先调用ccTouchBegan: withEvent:方法,该方法对每个UITouch进行响应并返回一个BOOL值,若为“YES”,则表明用户触摸事件已经被处理,后续的ccTouchMoved: withEvent:、ccTouchEnabled: withEvent:和ccTouchCancelled: withEvent:会接着响应,其他事件则不会再去进行监听。ccTouchBegan: withEvent:方法返回的值如果为假,则会继续交给其他注册过的类型进行处理。

接下来是ccTouchBegan: withEvent:方法和 ccTouchMoved: withEvent:方法的处理,实现代码如下(程序清单同上)。

<code>-(</code><code>BOOL</code><code>) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{</code>

<code>    </code><code>return</code> <code>YES;</code>

<code>- (</code><code>void</code><code>)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {</code>

<code>    </code><code>// 把touch坐标转换成局部node坐标</code>

<code>    </code><code>CGPoint touchLocation = [self convertTouchToNodeSpace:touch];</code>

<code>    </code><code>// 把旧坐标也转换成局部node坐标</code>

<code>    </code><code>CGPoint oldTouchLocation = [touch previousLocationInView:touch.view];</code>

<code>    </code><code>oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];</code>

<code>    </code><code>oldTouchLocation = [self convertToNodeSpace:oldTouchLocation];</code>

<code>    </code><code>// ccpSub计算两点的差异,即计算touch偏移量,把当前的点坐标减去上一个点坐标</code>

<code>    </code><code>CGPoint translation = ccpSub(touchLocation, oldTouchLocation);</code>

<code>    </code><code>// ccpAdd让两个坐标相加</code>

<code>    </code><code>CGPoint newPos = ccpAdd(planeSprite.position, translation);</code>

<code>    </code><code>// 给玩家飞机精灵设置新的坐标位置</code>

<code>    </code><code>planeSprite.position = newPos;</code>

再次编译并运行游戏,在模拟器中使用鼠标选择玩家飞机,然后移动鼠标,玩家飞机会随着鼠标轨迹移动。模拟器显示效果如图13.62所示。

疯狂ios讲义之疯狂打飞机(2)

现在,玩家飞机已经开始飞行,但是背景图片一直都没有变化,我们将为游戏添加连续滚动的背景,制作出更加逼真的飞行效果。步骤如下。

①打开HelloWorldLayer.m文件,添加一个变量:

<code>CCParallaxNode* backgroundNode;</code>

我们定义了一个CCParallaxNode(视差视图)变量,用来完成背景滚动效果。当移动时,会看到离我们越近的物体移动得越快,越远的物体,比如远处的山会移动得很慢,而最远处的物体,比如太阳几乎不动,这种现象叫视差,而在游戏中模仿视差,可以让玩家感觉到游戏中的角色的确是在移动。CCParallaxNode可以很容易地建立一个视差层,你可以控制每一层的视差率、位置和层级的高低;而CCFollow可以让你的层镜头跟随目标,所以,这里我们会使用CCParallaxNode和CCFollow给游戏添加连续滚动的背景效果。

②找到init方法,注释掉之前②添加背景图片的代码;再找到onEnter方法,添加两个背景图片用于拼接,实现代码如下(程序清单同上)。

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

<code>// ②添加连续滚动的背景</code>

<code>//初始化CCParallaxNode添加到当前层中</code>

<code>backgroundNode = [CCParallaxNode node];</code>

<code>[self addChild:backgroundNode z:-1];</code>

<code>// ratio指在CCParallaxNode移动时,添加进去的背景图片精灵的移动速度和CCParallaxNode的比率</code>

<code>CGPoint ratio = ccp(1.0,1.0);</code>

<code>// 屏幕高度480是iPhone Retina(3.5-inch),568是iPhone Retina(4-inch)</code>

<code>NSString *bgName;</code>

<code>if</code> <code>(screenHeight == 480) {</code>

<code>    </code><code>bgName = @</code><code>"bg1.png"</code><code>; </code><code>// 640*960</code>

<code>}</code><code>else</code><code>{</code>

<code>    </code><code>bgName = @</code><code>"bg2.png"</code><code>; </code><code>// 640*1136</code>

<code>// 第1张背景图</code>

<code>CCSprite *bgSprite1 = [CCSprite spriteWithFile:bgName];</code>

<code>// setAliasTexParameters用于解决拼接的地图在连接滚动时容易形成黑色缝隙的问题</code>

<code>[[bgSprite1 texture] setAliasTexParameters];</code>

<code>bgSprite1.anchorPoint = ccp(0,0);</code>

<code>[backgroundNode addChild:bgSprite1 z:1 parallaxRatio:ratio positionOffset:ccp(0,0)];</code>

<code>// 第2张背景图</code>

<code>CCSprite *bgSprite2 = [CCSprite spriteWithFile:bgName];</code>

<code>[[bgSprite2 texture] setAliasTexParameters];</code>

<code>bgSprite2.anchorPoint = ccp(0,0);</code>

<code>// positionOffset时在第2张背景图与第1个背景图拼接处减去1个像素,可以消除地图拼接的缝隙</code>

<code>[backgroundNode addChild:bgSprite2 z:1 parallaxRatio:ratio positionOffset:ccp(0, winSize.height - 1)];</code>

<code>// 添加开始连续滚动背景的代码</code>

<code>const</code> <code>int</code> <code>MAX_WIDTH = 320;</code>

<code>const</code> <code>int</code> <code>MAX_HEIGHT = 480 * 100;</code>

<code>CCSprite *hiddenPlaneSprite = [CCSprite spriteWithSpriteFrameName:@</code><code>"plane0.png"</code><code>];</code>

<code>hiddenPlaneSprite.visible = NO;</code>

<code>hiddenPlaneSprite.position = ccp(winSize.width / 2, winSize.height / 2);</code>

<code>[batchNode addChild:hiddenPlaneSprite z:-4 tag:1024];</code>

<code>id move = [CCMoveBy actionWithDuration:300.0f position:ccp(0,MAX_HEIGHT)];</code>

<code>[hiddenPlaneSprite runAction:move];</code>

<code>// 让背景开始滚动,背景跟随隐形飞机移动</code>

<code>[backgroundNode runAction:[CCFollow actionWithTarget:hiddenPlaneSprite</code>

<code>    </code><code>worldBoundary:CGRectMake(0, 0, MAX_WIDTH, MAX_HEIGHT)]];</code>

上面代码首先初始化了一个CCParallaxNode,并把它添加到当前层中。初始化两个背景图片精灵,设置锚点为(0,0),并把两个背景图片精灵都添加到CCParallaxNode当中。CCParallaxNode的ratio属性表示在CCParallaxNode移动时,添加进去的背景图片精灵的移动速度和CCParallaxNode的比率。假设CCParallaxNode是以每秒10像素移动的,如果设置的ratio是(1,1),那么bgSprite1和bgSprite2的X轴方向的速度也是每秒10像素,Y轴方向的速度也是每秒10像素。如果需要飞行速度加快,则可以将ratio设置为(1,2),读者可以自行设置来测试背景图片移动的速度。在两个背景图片精灵上面都调用了setAliasTexParameters方法,用于解决拼接的地图在连接滚动时容易形成黑色缝隙的问题。同时,在设置positionOffset时,在第2张背景图与第1张背景图拼接处减去1个像素,可以消除地图拼接的缝隙。

接下来是添加连续滚动背景的代码。这里创建了一个隐形的飞机精灵,因为想要实现背景的连续滚动,需要背景能够跟随一个不断向上前进的CCNode才会产生连续向下滚动的效果。让隐形的飞机精灵执行一个moveBy动作,在一定的时间范围内运行一定的距离,而backgroundNode则执行一个CCFollow跟随动作,跟随隐形飞机移动,从而达到背景滚动的效果。

③这里还需要一个算法来计算backgroundNode的坐标位置。读者可以在配套光盘的Resource目录下找到两个预先制作好的CCParallaxNode的扩展类:CCParallaxNode-Extras.h和CCParallaxNode-Extras.m,将这两个文件添加到项目的“AirfightGame”组中。

疯狂ios讲义之疯狂打飞机(2)

④打开HelloWorldLayer.m文件,在私有分类中添加一个更新背景图片滚动的方法,示例代码如下:

<code>-(</code><code>void</code><code>) updateBackground:(ccTime)delay;</code>

下面是该方法的具体实现,实现代码如下(程序清单同上)。

<code>-(</code><code>void</code><code>) updateBackground:(ccTime)delay{</code>

<code>    </code><code>CCSprite *sprite;</code>

<code>    </code><code>int</code> <code>index = 0;</code>

<code>    </code><code>CCARRAY_FOREACH([backgroundNode children],sprite){</code>

<code>    </code><code>CGPoint pt = [backgroundNode convertToWorldSpace:sprite.position];</code>

<code>        </code><code>if</code> <code>(pt.y &lt;= -sprite.contentSize.height) {</code>

<code>            </code><code>// sprite.contentSize.height表示精灵的高度,即背景图片的高度</code>

<code>            </code><code>[backgroundNode incrementOffset:ccp(0, (sprite.contentSize.height - 1) *</code>

<code>2.0f) forChild:sprite];</code>

<code>        </code><code>}</code>

<code>        </code><code>index++;</code>

updateBackground的作用是循环遍历backgroundNode中的背景图片精灵,即时修改背景图片精灵的坐标位置。

⑤找到onEnter方法,在最后添加游戏的主循环代码,实现代码如下。

<code>// ⑥游戏主循环,每帧都调用的更新方法</code>

<code>// 这样以默认cocos2d的刷新频率1/60.0s调用(void)update:(ccTime)delta一次</code>

<code> </code><code>[self scheduleUpdate];</code>

这个方法是游戏的主循环。任何游戏都包括一个游戏主循环,用来更新游戏的状态、玩家和敌人的数量、碰撞处理的逻辑等。

定义一个update方法,调用更新背景图片滚动的方法,实现代码如下(程序清单同上)。

<code>-(</code><code>void</code><code>) update:(ccTime)delta{</code>

<code>    </code><code>[self updateBackground:delta];</code>

再次编译并运行游戏,可以看到一个连续滚动的游戏背景效果。

疯狂ios讲义之疯狂打飞机(2)