我想没有什么比更新入门教程更适合用来庆祝具有标志性的Cocos2D 3.0的诞生了。
Cocos2D 3.0是iOS平台一款2D游戏开发框架的最新版本。App
Store上数以千计的游戏不乏一些排行前十的游戏都是用它来进行开发的。
它对sprite有很好的支持,包含一个完美的Chipmunk2D physics类库,支持OpenAL声音类库,诸多有趣的效果,让你可以用它做更多事情。
通过本Cocos2D 3.0的入门教程,你将会学习从头到尾的编写一个简单有趣的iPhone
2D游戏。如果你之前已经学习过了Cocos2D 2.0的教程,那么将会将会对本教程感觉比较熟悉,但是你仍然可以通过本教程学习利用物理引擎实现一些好玩的东西。
你可以选择跟随本教程进行学习,当然你也可以直接在本文的末尾下载例子源程序进行学习。那么接下来我们跟随忍者一起热血一下吧。
Cocos2D vs Sprite Kit
在你开始学习之前,你也许在想:“嘿,我已经有苹果的Sprite Kit了,还需要Cocos2D吗?我真的需要尝试一些其它的东东吗?”
好吧,跟其它任何游戏框架一样,Cocos2D也有它的一些优点和缺点:
Cocos2D 优点
优点一: 你不再被限制在iOS平台了,想要把你的游戏发布到Android平台上吗?那么用Cocos2D可以帮你写一遍代码就可以跨平台发布了!
优点二: 你可以写代码自定义一些OpenGL效果了。
优点三: 它是开源的,也就是说如果你想要改造写什么或者研究一下它内部是如何运转的,你只需要深入研究一下它的源码就可以了。
Cocos2D 缺点
缺点一: 它不是内建在Xcode里面的,所以你要使用它就必须要下载安装它。
缺点二: 它目前还没有自带的纹理与粒子编辑器(但是已经有很多很棒的第三方编辑器了)。
Cocos2D拥有一个完整的社区,社区里有很多相关的教程、书籍和例子程序。Cocos2D的缔造者是一波旨在令游戏开发变得简单的游戏开发者,他们希望开发者利用它能够简单地创造很多伟大的游戏。
安装 Cocos2D
Cocos2D 3.0 使用了一个全新的安装程序,安装起来没有比这更简单的了!
只需要 下载最新的Cocos2D 安装器 (3.0版本以上), 打开DMG文件然后双击安装器。随后安装器会自动帮你配置好Xcode的Cocos2D模板并且部署好Cocos2D的Xcode文档。
当安装开始的时候,你将会看到一大堆信息,待安装完成之后会自动打开Cocos2D的欢迎页面。恭喜你,你已经做好了使用Cocos2D的准备工作了!
Hello World
让我们使用上一步安装的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. 让我们仔细研究下这个场景。
忍者驾到!
在忍者做它漂亮的出场秀之前,你首先需要用到一些图片素材...
第一步:First step, 下载本工程的资源包。解压后拖动ResourcePack到项目的工程目录中。一定要勾选上
“Copy items into destination group’s folder (if needed)”选项并且勾选上Cocos2DSimpleGame目标target。
第二部:打开HelloWorldScene.m。记住,这段代码是用来构建那个旋转着Cocos2D
logo的场景的,这个场景可以作为游戏制作很好的起点。修改之前先看一下模板代码:
@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; } |
让我们一起逐行过一下:
为了接下来更方便地操作这个正在旋转的Cocos2D logo图标,我们为它声明一个私有实例。
初始化 HelloWorld 场景.
配置这个场景让它可以响应Cocos2D 的点击事件。在之后你会看到这这样配置使得 touchBegan:withEvent:
方法将要被调用到。
创建一个 CCNodeColor, 用它来展示一种颜色(本教程中用它展示深灰色)。当这个node创建之后,你需要调用
addChild: 把它加到当前场景之后才能看到它。现在你的场景拥有一个背景色了!
用 spriteWithImageNamed: 这个方法创建一个 CCSprite 并为它加载一个图片资源。通过使用这个屏幕的坐标,这个sprite的位置被放置在了屏幕的中央。再调用一下
addChild: 方法,把它也添加到场景中。
创建一个 CCActionRotateBy action,我们将用它来给这个sprite做360度旋转,配置
CCActionRepeatForever 参数让这个旋转动作无限循环下去。然后调用 runAction
方法让sprite接收这个action。在接下来的讨论中会发现Actions 是Cocos2D的一项非常强大的特性。
创建一个 CCButton 用以点击返回 IntroScene 场景, 你可以用这个操作作为重新初始化当前场景的一种简单方法。
很棒! 首先呢,让我们把旋转着的Cocos2D logo图标替换成忍者的。
想想该怎么做呢? 给你一些提示:
看一看你刚刚添加到项目中的 ResourcePack ,从里面找一找忍者的图片。
你只需要改动一行代码就可以了!
尝试尽量自己搞定它,但是如果你实在卡在这里了,那么方法就在下面
// 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图片已经被替换成忍者的了。但愿忍者不会感觉到晕。。。
然而,忍者这辈子就是被训练来战斗的,所以接下来你想要搞一些怪物让忍者挑战一下!
疯狂的怪物出现了
接下来你想要添加一些怪物到你的场景中。一个不会动的怪物自然不会对一个训练有素的忍者造成任何威胁,所以为了变得更有趣一些,你将添加一些会动的怪物。你将在屏幕的右外延创建一些怪物并且给它们添加
CCAction , 命令它们从屏幕右边向屏幕左边移动。
在 HelloWorldScene.m 中添加如下代码:
- (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]]]; } |
让我们一起逐行过一下:
为怪物的诞生位置定一个纵向范围。当你放置一个sprite的时候,默认的定位到的是sprite的中心坐标。所以这里为了不至于让怪物断掉,你在计算的时候需要用屏幕的高度减掉怪物的高度。
用屏幕的宽度加上怪物的宽度确保把怪物恰好定位到屏幕的右外延,不让怪物露出来。
现在你想要决定怪物横穿屏幕的用时,让我们定个随机时间吧,让怪物的移动更加难以预测。
你将使用Cocos2D的 CCActionMoveTo: action 来快速移动怪物从起点(屏幕右外延)到目的地(屏幕左外延)。
你已经看到旋转的action了,然而Cocos2D其实提供了很多非常方便的内置actions,包括移动的、旋转的、消失的、动画的等等。这里我们对怪物使用三种action:
CCActionMoveTo: 这个action被用作控制怪物的移动轨迹,在本教程中用于控制它从屏幕右侧移动到左侧。这个action的duration用于控制怪物执行动作所需的时间。Duration设置的越短,怪物移动的速度将越快。在这里你把duration设置成2秒到4秒之间的随机秒数。
CCActionRemove: 将node从它的parent移除的好方法,可以有效地将node从场景中删除。当看不到怪物的时候(怪物移动到屏幕做外延的时候)你可以使用这个action把怪物从这个场景中移除。这步是非常重要的,如果不这么做你会发现有成千上万的怪物试图穿越你的iPhone,它们会耗光你设备的全部资源。
CCActionSequence: 这个序列action可以让你把一组action串在一起按序列执行,同一时间只有一个action在执行。通过这个方法你可以设置
CCActionMoveTo action 首先执行,一旦这个action执行完成了接着执行 CCActionRemove
action。你可以用这个强大的action创建一组复杂的动画序列。
好的,现在你有了一个向场景添加怪物的方法了。然而对于一个经验丰富的忍者来说,一个怪物不足为惧,让我们写一个定时产生怪物的方法吧。
Cocos2D 提供了一个定时器帮助你每X.X秒调用一下回调方法,所以你可以利用它来完成每X秒生成怪物的方法。
打开 HelloWorldScene.m 然后在 onEnter 方法的 [super onEnter];
之后添加如下代码:
[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]]; |
目前你忍者的头一定还在旋转着,你能让它停止转动,并且把他移动到偏左一些这样便于让它做好准备面对前面的突击?
禁用头的旋转 CCActionRotateBy action。
// 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); |
你也许会疑惑为什么要把宽度除以8(屏幕的1/8)。这是因为如果图方便直接写死坐标,那么遇到不同分辨率设备的时候就需要每种设备设置不同的坐标。
如果你感兴趣想要多了解一些,看一下 CCButton 的位置配置方式,注意它使用了一种不同的 positionType
,它使用了标准化的解决方案0..1,0..1针对所有设备。
编译运行,你应该可以看到很多怪物穿越屏幕!
波动拳(Hadouken)
不幸的是你的忍者还没有足够强大到可以发射波动拳,所以你将需要依靠你专业的投掷技巧来抵御邪恶的(或者可能只是误解)怪物。
给自己那个手里剑然后添加个投掷的action
你将再次使用 CCActionMoveTo: action, 然而这次不会像是从 _player.position
移动到点击位置那般简单。 你想要将你的投射物沿着点击的方向穿越整个屏幕。因此你需要用到一点数学只是。
你可以从起点到点击点的x轴、y轴的位移变化中得到一个小的三角形。你只需求得一个拥有相同角度并且终止点在屏幕边沿的大三角形就OK了。
为了完成这个运算,如果你有一些向量数学的基础就轻松多了(比如知道增加和减少向量的方法)。 Cocos2D
包含了一系列方便的向量操作函数,如 ccpAdd 和 ccpSub 。
如果你对接下来的计算有任何疑问,请速查 vector math explanation。关于这个方面我个人建议看一下非常好的
Khan Academy 教学视频。
响应点击了!
正如你所看到的HelloWorldScene 模板已经在 init 方法中允许接收touch相应了。
- (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]]]; } |
让我们一起逐行过一下:
你需要将屏幕点击转化为场景的坐标系表示。Cocos2D的UITouch分类里有一个很方便的方法 locationInNode:
可以做的这一点。
所以正如你所见,你得到了一个由起点到点击点的x、y轴位移组成的小三角形。你只需求得一个拥有相同角度并且终止点在屏幕边沿的大三角形就OK了。
创建投掷物的sprite并且设置它的起始位置与忍者一样。你可以简单的使用 _player ,这就是你之前设置这个私有实例变量的原因。
你现在应该对CCActionMoveTo 这个方法更加熟悉了。你有了之前计算的目标点和投掷物投掷完成的时间duration(duration越小,投掷物就会越快投过去)。
编译运行,尽情开火吧!
Arggghhh 这些怪物太强大了,我们为什么不把他们给干掉呢!
碰撞检测和物理
那么现在你有了一个忍者、很多怪物和很多穿越屏幕的手里剑。看起来不错了,不过如果带一点摩擦的话就更好玩了,为了达成这一目的你需要在投掷物跟怪物之间做碰撞检测。
Cocos2D 3.0的一个伟大的特性就是一套完整的物理引擎,有了它完成这个任务就小菜一碟了。物理引擎的伟大在于模拟了真实的移动,而且它对于处理碰撞检测也非常有用。
你现在将要使用Cocos2D的物理引擎去判定怪物和抛掷物的碰撞。完成它需要四个步骤:
搭建物理世界. 物理世界是针对物理学计算的模拟空间。你将为当前场景搭建一个并且修改一些属性例如重力。物理世界搭建完成之后,你需要修改现有的游戏对象,把他们添加到物理世界中进而他们都将成为物理模拟的一部分。
为每个sprite创建物理主体. 使用Cocos2D你可以为每一个sprite关联物理主体用于碰撞检测并且可以设置某些属性。记住物理主体不用必须跟sprite的形状完全一样。通常它是一些简单的矩形或者圆形,甚至是一个像素清晰的轮廓。这样已经可以很好的满足大部分游戏并且可以提供更好的物理效果处理效率。
为每一种sprite设置CollisionType. Cocos2D物理引擎的一个方便特性是你不需要用数字定义你的碰撞类型了。你可以配置一个简单易懂的字符串定义你的碰撞类型。
配置碰撞相应代理. 默认当连个物理主体碰在一起他们会被物理模拟器处理,然而你想要在投掷物碰到怪物的时候做一些事情例如干掉怪物。因此你需要添加一个碰撞响应代理去处理投掷物与怪物之间的碰撞类型。
让我们开始吧。首先,你现在需要为你的物理世界添加另外一个私有实例变量。
打开 HelloWorldScene.m 然后在 @implementation HelloWorldScene
声明里面 CCSprite 的初始化之后添加如下代码:
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 中匹配碰撞的响应处理。
你会注意到Xcode在 collisionDelegate 这行抛出了一个警告; 这很容易解决。打开
HelloWorldScene.h 然后标志这个 interface 实现CCPhysicsCollisionDelegate。
@interface HelloWorldScene : CCScene <CCPhysicsCollisionDelegate> |
现在你需要为忍者配置物理主体并且将它添加到 _physicsWorld 而不是直接添加到场景中。
退回到 HelloWorldScene.m, 在 init 方法中找到如下代码:
将它替换成如下代码:
_player.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, _player.contentSize} cornerRadius:0]; // 1 _player.physicsBody.collisionGroup = @"playerGroup"; // 2 [_physicsWorld addChild:_player]; |
快速看一下这个代码片段:
创建一个物理主体,在这个例子中使用忍者的 contentSize 来创建一个环绕在忍者四周的矩形主体。
设置物理主体的 collisionGroup, 默认所有的主体都会碰撞。如果你将一些物理主体设置成相同的
collisionGroup 他们之间就不会再互相碰撞了, 这点当你想要创建一个由多个身体组成的角色,而且不想让他的每个身体互相碰撞的时候非常有用,例如一个拿着武器的角色。你将用这个保证投掷物不会撞到角色。
你已经配置好物理模拟器、为角色创建了物理主体并且把它添加到了物理模拟器中。现在想想你能不能自己为怪物也添加到物理模拟器中。
查找 addMonster: 方法并且定位到如下代码:
将它替换成:
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 搭建一个物理模拟器碰撞代理方法。
你快搞定它了!这次试着把抛掷物添加到模拟器中。我会给你一个提示:搞定它需要使用到 collisionType
和 collisionGroup 属性。
找到 touchBegan:withEvent 方法然后定位到如下代码:
[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 属性创建的。在你首次搭建物理世界的时候他们是很有用的,你可以通过他们确认一切工作是否如你所愿的进行着。注意到在手里剑周围的方块不是很和谐;如果用圆形的话应该更合适一些。
当然有这么个方法bodyWithCircleOfRadius:可以用于创建更时候你的抛掷物的圆形物理主体。将抛掷物物理主体的配置代码替换为如下代码:
projectile.physicsBody =
[CCPhysicsBody bodyWithCircleOfRadius:projectile.contentSize.width/2.0f andCenter:projectile.anchorPointInPoints]; |
默认中心点会被设置在sprite的左下方,然而你想把圆形放置在你的sprite的中心处。
好的,你现在已经把你的游戏对象改成物理模拟器的一部分了。现在你一定想要在 projectileCollision
和monsterCollison collisionType 交流的时候做一些事情。
Cocos2D物理引擎有很多很好的方法去完成这个目的。只需要在 HelloWorldScene.m中添加如下方法:
- (BOOL)ccPhysicsCollisionBegin:
(CCPhysicsCollisionPair *)pair monsterCollision:(CCNode *)monster projectileCollision:(CCNode *)projectile { [monster removeFromParent]; [projectile removeFromParent]; return YES; } |
这段代码很强大。当物理模拟器被搭建完成之后,物理引擎会去查找CCPhysicsCollisionDelegate
方法,如果找到了就会触发它。参数名设置成你想要配置成的collisionType。
用这个方法你把 ‘projectile’ 和 ‘monster’彻底从模拟器和场景中移除了。你当然可以添加一个计分器,添加一个特殊效果或者其它你想在抛掷物与怪物碰撞时发生的事情。
编译运行,你最后应该可以毁灭这些怪物了。忍者,前进,开火!
收尾
你现在已经离完成一个简单游戏非常近了。但是 (Pew-Pew!), 添加一些音乐对于现在来说再好不过了。
Cocos2D 用OpenAL声音类库提供声音支持。不需要添加任何的额外头文件,都为你准备好了。
历史课程: 对于那些使用Cocos2D之前版本的用户也许会疑惑SimpleAudioEngine怎么了,
这个声音类库已经被Open AL取代了。
播放一个SFX非常简单, 是时候在忍者每次发镖的时候添加一个SFX了。这个声音可能不能100%完美表现出忍者投掷手里剑的声音。:-)
在创建抛掷物的 touchBegan: 方法的最后添加如下代码:
[[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 ,随时都可以过来跟我交流。
如果你对这篇教程有任何疑问或者建议请一起讨论! |