在第一部分里,我们从touches对象集合中选择一个,得到他在当前屏幕上的坐标,通过调用convertToGL来把此坐标转换为当前屏幕模式适应的,这一点很重要,因为我们使用的是landscape模式。
接下来加载飞镖sprite并像往常一样设置其初始坐标。根据之前讨论过的相似三角形算法,我们计算得到飞镖的终点。
注意这个算法并不完美。即使飞镖的Y坐标已经出了屏幕,它还是会被强制移动到X坐标移出屏幕为止。想解决这个有很多手段,包括检测射击点到屏幕边缘的最短距离,在游戏逻辑的回调里检测飞镖坐标是否在屏幕之外(比如visit),虽然这些方法都可以解决它,但简单起见,这篇初学者教程还是会使用之前的方式。
最后一件事儿是决定运动所需的时间。飞镖最好是能以固定的速率射出,所以我们还需要一丁点儿数学计算。使用 勾股定理可以轻松地得到斜边的长度。
一旦有了距离,只需要将其除以一个固定的速率,便可得到时间。
剩余部分就像我们之前做的,给靶子设置上actions。编译并运行,哈哈,你的忍者能给予冲过来的鬼头靶子致命打击了!
碰撞检测Collision Detection
飞镖四处飞,但我们的小忍者并没有看到飞镖击倒鬼头靶子。是时候加入一些检测飞镖与靶子碰撞检测的代码了。
使用Cocos2D有很多途径解决这个,包括使用其中一个引擎包含的物理引擎:Box2D或者Chipmunk。为了有效说明问题的本质并使事情简单,我们将自己实现一个简单的碰撞检测。
首先,我们需要记录所有在当前场景里的靶子和飞镖对象。在HelloWorldLayer类的声明中加入:
1 2 |
NSMutableArray
*_targets;
NSMutableArray *_projectiles; |
并在init方法里加入初始化数组的代码:
1 2 |
_targets
=
[
[
NSMutableArray alloc
] init
];
_projectiles = [ [ NSMutableArray alloc ] init ]; |
在dealloc方法里边清理之:
1 2 3 4 |
[_targets release
];
_targets = nil; [_projectiles release ]; _projectiles = nil; |
现在,修改addTarget方法,在靶子数组中加入一个新靶子并设置其标识(tag)留待后用:
1 2 |
target.tag
=
1;
[_targets addObject :target ]; |
同样的,把在ccTouchesEnded里新建的飞镖加入到飞镖数组中并设置tag留待后用:
1 2 |
projectile.tag
=
2;
[_projectiles addObject :projectile ]; |
最后,修改spriteMoveFinished方法,根据tag分类把即将删除的对象从数组中也移除掉:
1 2 3 4 5 |
if
(sprite.tag
==
1
)
{
// target
[_targets removeObject :sprite ]; } else if (sprite.tag == 2 ) { // projectile [_projectiles removeObject :sprite ]; } |
编译并运行,确保到目前为止一切都OK。虽然目前还看不到什么显著的变化,但我们已经有了做碰撞检测的基础了。
添加以下方法到HelloWorldLayer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
-
(
void
)update
:
(ccTime
)dt
{
NSMutableArray *projectilesToDelete = [ [ NSMutableArray alloc ] init ]; for (CCSprite *projectile in _projectiles ) { CGRect projectileRect = CGRectMake ( projectile.position.x - (projectile.contentSize.width / 2 ), projectile.position.y - (projectile.contentSize.height / 2 ), projectile.contentSize.width, projectile.contentSize.height ); NSMutableArray *targetsToDelete = [ [ NSMutableArray alloc ] init ]; for (CCSprite *target in _targets ) { CGRect targetRect = CGRectMake ( target.position.x - (target.contentSize.width / 2 ), target.position.y - (target.contentSize.height / 2 ), target.contentSize.width, target.contentSize.height ); if (CGRectIntersectsRect (projectileRect, targetRect ) ) { [targetsToDelete addObject :target ]; } } for (CCSprite *target in targetsToDelete ) { [_targets removeObject :target ]; [self removeChild :target cleanup : YES ]; } if (targetsToDelete.count > 0 ) { [projectilesToDelete addObject :projectile ]; } [targetsToDelete release ]; } for (CCSprite *projectile in projectilesToDelete ) { [_projectiles removeObject :projectile ]; [self removeChild :projectile cleanup : YES ]; } [projectilesToDelete release ]; } |
以上内容很清晰。我们遍历了一下飞镖和靶子数组,在遍历中得到每一个对象的bounding box(碰撞框),使用CGRectIntersectsRect来检测两个矩形是否相交。如果有相交,则将飞镖和靶子分别从场景和数组中移除。注意我们必须将这些对象加入到“toDelete”结尾的数组中,因为我们没法在当前循环中从数组中删掉它。还是一样,有很多更优的方法来解决这类问题,简单起见,我使用最简单的方法。
你仅仅需要一步就可以欢呼了,使用schedule调度这个方法,使其在每一帧都执行:
1 |
[self schedule
:
@selector
(update
:
)
];
|
编译并运行,所有和飞镖碰撞的靶子都会灰飞烟灭了!
完成触摸Finishing Touches
我们已经非常接近制作出一个成品(但很简单)的游戏了。只需要再加入一些音效和音乐(没有游戏不带声音的!)以及一些简单的游戏逻辑。
首先,把背景音乐和一个射击音效拖拽进你的工程的resources文件夹。你可以随意使用以下资源cool background music I made or my awesome pew-pew sound effect, 或者制作你自己的。
接下来在HelloWorldLayer.m的一开头导入头文件:
1 |
#import "SimpleAudioEngine.h"
|
在init方法中,如下所示播放背景音乐:
1 |
[
[SimpleAudioEngine sharedEngine
] playBackgroundMusic
:
@
"background-music-aac.caf"
];
|
在ccTouchesEnded方法中播放音效:
1 |
[
[SimpleAudioEngine sharedEngine
] playEffect
:
@
"pew-pew-lei.caf"
];
|
现在,我们加入一个提示“You Win”或者“You Lose”的游戏结束场景。右键点击Classes文件夹,选择FileNew File,并选择Objective-C class,确保继承的类是NSObject。点击Next,在filename位置输入GameOverScene作为文件名,确保“Also create GameOverScene.h”是选中的。
用以下内容替换掉GameOverScene.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#import "cocos2d.h"
@interface GameOverLayer : CCLayerColor { CCLabelTTF *_label; } @property (nonatomic, retain ) CCLabelTTF *label; @end @interface GameOverScene : CCScene { GameOverLayer *_layer; } @property (nonatomic, retain ) GameOverLayer *layer; @end |
用以下内容替换掉GameOverScene.m:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#import "GameOverScene.h"
#import "HelloWorldLayer.h" @implementation GameOverScene @synthesize layer = _layer; - ( id )init { if ( (self = [super init ] ) ) { self.layer = [GameOverLayer node ]; [self addChild :_layer ]; } return self; } - ( void )dealloc { [_layer release ]; _layer = nil; [super dealloc ]; } @end @implementation GameOverLayer @synthesize label = _label; - ( id ) init { if ( (self = [super initWithColor :ccc4 ( 255, 255, 255, 255 ) ] ) ) { CGSize winSize = [ [CCDirector sharedDirector ] winSize ]; self.label = [CCLabelTTF labelWithString : @ "" fontName : @ "Arial" fontSize : 32 ]; _label.color = ccc3 ( 0, 0, 0 ); _label.position = ccp (winSize.width / 2, winSize.height / 2 ); [self addChild :_label ]; [self runAction : [CCSequence actions : [CCDelayTime actionWithDuration : 3 ], [CCCallFunc actionWithTarget :self selector : @selector (gameOverDone ) ], nil ] ]; } return self; } - ( void )gameOverDone { [ [CCDirector sharedDirector ] replaceScene : [HelloWorldLayer scene ] ]; } - ( void )dealloc { [_label release ]; _label = nil; [super dealloc ]; } @end |
注意这里有两个不同的对象:一个scene和一个layer。一个scene可以包含很多layer。不过目前这个只有一个,这个layer只是放置了一个label在屏幕中心,并schedule了一个3秒会自动返回Hello World场景的事件。
最后,加入一些极为简单的游戏逻辑进去。首先,记录一下被主人公的飞镖干掉的靶子。在HelloWorldLayer类中加入一个成员变量,在HelloWorldLayer.h中的@interface块儿加入如下:
1 |
int _projectilesDestroyed;
|
在HelloWorldLayer.m里,导入GameOverScene类:
1 |
#import "GameOverScene.h"
|
在update方法中,增加飞镖摧毁靶子的数量并检测获得胜利的条件,在removeChild:target:之后加入:
1 2 3 4 5 6 7 |
_projectilesDestroyed
++;
if (_projectilesDestroyed > 30 ) { GameOverScene *gameOverScene = [GameOverScene node ]; _projectilesDestroyed = 0; [gameOverScene.layer.label setString : @ "You Win!" ]; [ [CCDirector sharedDirector ] replaceScene :gameOverScene ]; } |
当有鬼头靶子越过了屏幕左侧,你就输了,修改spriteMoveFinished方法,在tag == 1 case中removeChild:sprite:方法之后加入:
1 2 3 |
GameOverScene
*gameOverScene
=
[GameOverScene node
];
[gameOverScene.layer.label setString : @ "You Lose :[" ]; [ [CCDirector sharedDirector ] replaceScene :gameOverScene ]; |
编译并执行,现在你胜利或失败后会进入game over场景中,根据条件的不同会显示相应信息。
源码全部给你!Gimme The Code!
就到这里!这是到目前为止所有的源码下载地址simple Cocos2D iPhone Game