我想没有什么比更新入门教程更适合用来庆祝具有标志性的Cocos2D 3.0的诞生了。
Cocos2D 3.0 使用了一个全新的安装程序,安装起来没有比这更简单的了!
只需要 下载最新的Cocos2D 安装器 (3.0版本以上), 打开DMG文件然后双击安装器。随后安装器会自动帮你配置好Xcode的Cocos2D模板并且部署好Cocos2D的Xcode文档。
当安装开始的时候,你将会看到一大堆信息,待安装完成之后会自动打开Cocos2D的欢迎页面。恭喜你,你已经做好了使用Cocos2D的准备工作了!
让我们使用上一步安装的Cocos2D模板搭建一个简单的Hello World游戏作为开始吧。
打开Xcode,选择 File\New Project , 然后选择 iOS\cocos2d v3.x\cocos2d iOS 模板,点击 Next:
Product Name 输入Cocos2DSimpleGame, 设备选择iPhone 然后点击Next:
选择一个位置保存你的工程,然后点击Create。然后直接点击play按钮编译运行一下项目。你会看到下面的东东:
点击 Simple Sprite 按钮切换到另一个测试场景,接下来我们将一起针对改场景进行改造:
Cocos2D引入了场景scenes的概念,你可以把它想成是游戏的多个屏幕。HelloWorld游戏的第一个场景就是它的菜单场景IntroScene, 第二个场景就是转动着Cocos2D logo标志的HelloWorldScene. 让我们仔细研究下这个场景。
@implementation HelloWorldScene { // 1 CCSprite *_sprite; }
- (id)init { // 2 self = [super init]; if (!self) return(nil); // 3 self.userInteractionEnabled = YES; // 4 CCNodeColor *background = [CCNodeColor nodeWithColor:[CCColor colorWithRed:0.2f green:0.2f blue:0.2f alpha:1.0f]]; [self addChild:background]; // 5 _sprite = [CCSprite spriteWithImageNamed:@"Icon-72.png"]; _sprite.position = ccp(self.contentSize.width/2,self.contentSize.height/2); [self addChild:_sprite]; // 6 CCActionRotateBy* actionSpin = [CCActionRotateBy actionWithDuration:1.5f angle:360]; [_sprite runAction:[CCActionRepeatForever actionWithAction:actionSpin]]; // 7 CCButton *backButton = [CCButton buttonWithTitle:@"[ Menu ]" fontName:@"Verdana-Bold" fontSize:18.0f]; backButton.positionType = CCPositionTypeNormalized; backButton.position = ccp(0.85f, 0.95f); // Top Right of screen [backButton setTarget:self selector:@selector(onBackClicked:)]; [self addChild:backButton]; return self; }
让我们一起逐行过一下:
// 5 _sprite = [CCSprite spriteWithImageNamed:@"player.png"];
这很简单, _sprite 是一个很明显的名字但是当你开始使用_sprite1, _sprite2 的时候它会显得有一点混乱,所以我们把 _sprite 改成 _player。在 @implementation 中找到第一项:
@implementation HelloWorldScene { // 1 CCSprite *_sprite; }
然后把它改成:
@implementation HelloWorldScene { // 1 CCSprite *_player; }
很快你就会发现Xcode给你标志出5个错误并且帮你把这错误的几行用红色高亮了。不用担心,这是因为你把 _sprite 改名成了 _player,导致 _sprite 失效的原因。所以继续把所有的 _sprite 引用都改成 _player. 就OK了。
现在旋转着的Cocos2D logo图片已经被替换成忍者的了。但愿忍者不会感觉到晕。。。
然而,忍者这辈子就是被训练来战斗的,所以接下来你想要搞一些怪物让忍者挑战一下!
- (void)addMonster:(CCTime)dt { CCSprite *monster = [CCSprite spriteWithImageNamed:@"monster.png"]; // 1 int minY = monster.contentSize.height / 2; int maxY = self.contentSize.height - monster.contentSize.height / 2; int rangeY = maxY - minY; int randomY = (arc4random() % rangeY) + minY; // 2 monster.position = CGPointMake(self.contentSize.width + monster.contentSize.width/2, randomY); [self addChild:monster]; // 3 int minDuration = 2.0; int maxDuration = 4.0; int rangeDuration = maxDuration - minDuration; int randomDuration = (arc4random() % rangeDuration) + minDuration; // 4 CCAction *actionMove = [CCActionMoveTo actionWithDuration:randomDuration position:CGPointMake(-monster.contentSize.width/2, randomY)]; CCAction *actionRemove = [CCActionRemove action]; [monster runAction:[CCActionSequence actionWithArray:@[actionMove,actionRemove]]]; }
让我们一起逐行过一下:
[self schedule:@selector(addMonster:) interval:1.5];
这样会创建一个每1.5秒调用 addMonster: 方法的Cocos2D 定时器。
记住当你创建 addMonster 方法的时候, 会有一个额外的 dt 参数。 这代表着时间增量,也表示当前帧与之前的时间差异。定时器要求每个方法声明的时候都加上这个参数,虽然在这个教程里面你将不会使用到它。
// 2 CCNodeColor *background = [CCNodeColor nodeWithColor:[CCColor colorWithRed:0.6f green:0.6f blue:0.6f alpha:1.0f]];
目前你忍者的头一定还在旋转着,你能让它停止转动,并且把他移动到偏左一些这样便于让它做好准备面对前面的突击?
// 6 //CCActionRotateBy* actionSpin = [CCActionRotateBy actionWithDuration:1.5f angle:360]; //[_player runAction:[CCActionRepeatForever actionWithAction:actionSpin]];改变忍者的位置。
_player.position = ccp(self.contentSize.width/8,self.contentSize.height/2);
不幸的是你的忍者还没有足够强大到可以发射波动拳,所以你将需要依靠你专业的投掷技巧来抵御邪恶的(或者可能只是误解)怪物。
给自己那个手里剑然后添加个投掷的action
你可以从起点到点击点的x轴、y轴的位移变化中得到一个小的三角形。你只需求得一个拥有相同角度并且终止点在屏幕边沿的大三角形就OK了。
为了完成这个运算,如果你有一些向量数学的基础就轻松多了(比如知道增加和减少向量的方法)。 Cocos2D 包含了一系列方便的向量操作函数,如 ccpAdd 和 ccpSub 。
如果你对接下来的计算有任何疑问,请速查 vector math explanation。关于这个方面我个人建议看一下非常好的 Khan Academy 教学视频。
// 3 self.userInteractionEnabled = YES;
为了处理点击相应你通常需要创建 touchBegan: 方法,不过默认模板已经很体贴地给你带来了一个简单的 touchBegan 方法实例。
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event { // 1 CGPoint touchLocation = [touch locationInNode:self]; // 2 CGPoint offset = ccpSub(touchLocation, _player.position); float ratio = offset.y/offset.x; int targetX = _player.contentSize.width/2 + self.contentSize.width; int targetY = (targetX*ratio) + _player.position.y; CGPoint targetPosition = ccp(targetX,targetY); // 3 CCSprite *projectile = [CCSprite spriteWithImageNamed:@"projectile.png"]; projectile.position = _player.position; [self addChild:projectile ]; // 4 CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.5f position:targetPosition]; CCActionRemove *actionRemove = [CCActionRemove action]; [projectile runAction:[CCActionSequence actionWithArray:@[actionMove,actionRemove]]]; }
让我们一起逐行过一下:
所以正如你所见,你得到了一个由起点到点击点的x、y轴位移组成的小三角形。你只需求得一个拥有相同角度并且终止点在屏幕边沿的大三角形就OK了。
你现在应该对CCActionMoveTo 这个方法更加熟悉了。你有了之前计算的目标点和投掷物投掷完成的时间duration(duration越小,投掷物就会越快投过去)。
Arggghhh 这些怪物太强大了,我们为什么不把他们给干掉呢!
那么现在你有了一个忍者、很多怪物和很多穿越屏幕的手里剑。看起来不错了,不过如果带一点摩擦的话就更好玩了,为了达成这一目的你需要在投掷物跟怪物之间做碰撞检测。
Cocos2D 3.0的一个伟大的特性就是一套完整的物理引擎,有了它完成这个任务就小菜一碟了。物理引擎的伟大在于模拟了真实的移动,而且它对于处理碰撞检测也非常有用。
配置碰撞相应代理. 默认当连个物理主体碰在一起他们会被物理模拟器处理,然而你想要在投掷物碰到怪物的时候做一些事情例如干掉怪物。因此你需要添加一个碰撞响应代理去处理投掷物与怪物之间的碰撞类型。
CCPhysicsNode *_physicsWorld;
现在你需要配置并添加这个物理模拟器到你的场景中,在 init 方法的 CCNodeColor 之后添加如下代码:
_physicsWorld = [CCPhysicsNode node]; _physicsWorld.gravity = ccp(0,0); _physicsWorld.debugDraw = YES; _physicsWorld.collisionDelegate = self; [self addChild:_physicsWorld];
因为你要使用物理模拟器主要为了做碰撞检测,在这里把重力(Gravity)设置成 (0,0) 。Cocos2D 有很多方便的调试函数,debugDraw 标志对于具体数字化物理世界是非常有用的。你可以看到所有被添加到模拟器上的物理主体。你同时需要设置 collisionDelegate 到 self, 这样使得你可以添加碰撞处理到场景中而且物理模拟器知道去 HelloWorldScene 中匹配碰撞的响应处理。
@interface HelloWorldScene : CCScene <CCPhysicsCollisionDelegate>
现在你需要为忍者配置物理主体并且将它添加到 _physicsWorld 而不是直接添加到场景中。
[self addChild:_player];
将它替换成如下代码:
_player.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _player.contentSize} cornerRadius:0]; // 1 _player.physicsBody.collisionGroup = @"playerGroup"; // 2 [_physicsWorld addChild:_player];
快速看一下这个代码片段:
你已经配置好物理模拟器、为角色创建了物理主体并且把它添加到了物理模拟器中。现在想想你能不能自己为怪物也添加到物理模拟器中。
[self addChild:monster];将它替换成:
monster.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, monster.contentSize} cornerRadius:0]; monster.physicsBody.collisionGroup = @"monsterGroup"; monster.physicsBody.collisionType = @"monsterCollision"; [_physicsWorld addChild:monster];
这几乎是跟为 _player 添加物理主体的方式的一致的,但是我为了介绍一个新属性使了点小聪明。这次你设置了 collisionType 属性, 这将被用来为’monsterCollision’和’projectileCollsion’的 collisionType 搭建一个物理模拟器碰撞代理方法。
[self addChild:projectile];将它替换成:
projectile.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, projectile.contentSize} cornerRadius:0]; projectile.physicsBody.collisionGroup = @"playerGroup"; projectile.physicsBody.collisionType = @"projectileCollision"; [_physicsWorld addChild:projectile];
编译运行,你应该会看到很多漂亮的粉红色的小方块。
这些sprite周围的粉红色方块是被 _physics 的 debugDraw 属性创建的。在你首次搭建物理世界的时候他们是很有用的,你可以通过他们确认一切工作是否如你所愿的进行着。注意到在手里剑周围的方块不是很和谐;如果用圆形的话应该更合适一些。
projectile.physicsBody = [CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints];
默认中心点会被设置在sprite的左下方,然而你想把圆形放置在你的sprite的中心处。
好的,你现在已经把你的游戏对象改成物理模拟器的一部分了。现在你一定想要在 projectileCollision 和monsterCollison collisionType 交流的时候做一些事情。
- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair monsterCollision:(CCNode *)monster projectileCollision:(CCNode *)projectile { [monster removeFromParent]; [projectile removeFromParent]; return YES; }
这段代码很强大。当物理模拟器被搭建完成之后,物理引擎会去查找CCPhysicsCollisionDelegate 方法,如果找到了就会触发它。参数名设置成你想要配置成的collisionType。
用这个方法你把 ‘projectile’ 和 ‘monster’彻底从模拟器和场景中移除了。你当然可以添加一个计分器,添加一个特殊效果或者其它你想在抛掷物与怪物碰撞时发生的事情。
编译运行,你最后应该可以毁灭这些怪物了。忍者,前进,开火!
你现在已经离完成一个简单游戏非常近了。但是 (Pew-Pew!), 添加一些音乐对于现在来说再好不过了。
历史课程: 对于那些使用Cocos2D之前版本的用户也许会疑惑SimpleAudioEngine怎么了, 这个声音类库已经被Open AL取代了。
播放一个SFX非常简单, 是时候在忍者每次发镖的时候添加一个SFX了。这个声音可能不能100%完美表现出忍者投掷手里剑的声音。:-)
[[OALSimpleAudio sharedInstance] playEffect:@"pew-pew-lei.caf"];
是时候添加一些热血的音乐了,在 init 方法的 userInteractionEnabled 之后添加如下代码:
[[OALSimpleAudio sharedInstance] playBg:@"background-music-aac.caf" loop:YES];
最后一步,注释掉 init 方法中的调试语句:
_physicsWorld.debugDraw = YES;
编译运行, pew-pew. 你现在有声音了,简单不?
完工了! Cocos2D 3.0 game 这里有你目前编写的完整的代码。
你已经在学习 Cocos2D 3.0 的道路上走了一大段路了,向你介绍了一些Cocos2D的核心内容。伟大始于渺小,所以你为什么不通过找个项目再做一些更加深入的研究呢?
看一下 IntroScene 的代码你将会学到如何创建一个label。为何不加一个记录干掉怪物数量的计数器呢?
当忍者碰到怪物的时候会发生什么呢?
想要尝试一些更高大上的动画的话,在Xcode里面打开 Help\Documentation and API References 然后搜索 CCAction ,你会看到所有的你可以添加到你的游戏角色对象上的动画列表。
我从来没有见过不会转的手里剑,为啥不尝试给它加一点旋转(我确定在这个教程开始的时候我们看到过一个现成的例子)提示:你可以对一个node多次使用 runAction 。 这些action没必要放在一个序列中。
如果你想要学到更多关于Cocos2D的知识,official Cocos2D forum 是一个询问问题和学习经验的好地方。我的用户名是 @cocojoe ,随时都可以过来跟我交流。
如果你对这篇教程有任何疑问或者建议请一起讨论!