重复动作
So far, so good, but what if you want the cat lady to repeat this sequence multiple times? Of course, there’s an action for that!
现在看来还不错,但是如果你希望老太太多遛几个弯,当然,我们也有对应的action!
You can repeat an action a certain number of times using repeatAction:count:, or an endless number of times using repeatActionForever:.
你可以通过repeatAction:count:方法重复一个显存的动作,或者通过repeatActionForever:无限重复下去.
Let’s go with the endless variant. Replace the line that runs your action in spawnEnemy with the following two lines:
来试试无限循环的版本.把spawnEnemy方法中调用action的代码替换一下:
SKAction *repeat = [SKAction repeatActionForever:sequence];
[enemy runAction:repeat];
Congratulations, now you understand many useful types of actions:
恭喜,现在我们已经明白了很多有用的action:
• Move actions 移动
• Sequence actions 序列
• Wait for duration actions 等待
• Run block and selector actions 调用
Reversing actions 反向
• Repeat actions 重复
Next, you’re going to put these all together in a new and interesting way: you’re going to make cat ladies spawn periodically over time, instead of spawning just one cat lady at launch.
接下来尝试一点新东西:让老太太随机出现而不是开始的时候有一个.
Periodic spawning
周期生成
To prepare for periodic spawning, you’ll revert the spawnEnemy code to the original version that simply moves the cat lady from right to left. You’ll also introduce some random variance so the cat lady doesn’t always spawn at the same y-position.
想要周期生成一个sprite,我们需要把spwanEnemy方法恢复到最初那个单纯把老老太太龙右往左移的版本.我们还需要用到一些随机的变量来使老太太站在y轴的不同位置上.
First things first: you need a helper method to generate a random number within a range of values. Add this new method to the top of your file, along with the other math utilities you added in the first challenge in the previous chapter:
首先,我们需要创建一个用来在一定范围内生成随机数的辅助方法.把下列方法添加到文件的顶部,把这个方法跟上一章的挑战中创建的方法放到一起:
#define ARC4RANDOM_MAX 0x100000000
static inline CGFloat ScalarRandomRange(CGFloat min,CGFloat max)
{
return floorf(((double)arc4random() / ARC4RANDOM_MAX) *(max - min) + min);
}
Next, replace the current version of spawnEnemy with the following
下一步把spawnEnemy方法替换一下:
- (void)spawnEnemy {
SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed:@"enemy"];
enemy.position = CGPointMake( self.size.width + enemy.size.width/2, ScalarRandomRange(enemy.size.height/2,self.size.height - enemy.size.height/2));
[self addChild:enemy];
SKAction *actionMove = [SKAction moveToX:-enemy.size.width/2 duration:2.0];
[enemy runAction:actionMove];
}
All you did here was modify the fixed y-position to be a random value between the bottom and top of the screen, and revert the movement back to the original implementation. Well, the moveToX:duration: variant of the original implementation, anyway.
这里我们所做的只不过是把代码恢复到之前的版本,然后将固定的y左边改成了一个限定在屏幕内的随机值.恩...还有这个moveToX:duration:方法...
Now it’s time for some action. Inside initWithSize:, replace the call to spawnEnemy: with the following:
到了搞点动作的时候了.把initWithSize:中调用spawnEnemy:方法的代码替换成:
[self runAction:[SKAction repeatActionForever: [SKAction sequence:@[[SKAction performSelector:@selector(spawnEnemy) onTarget:self],[SKAction waitForDuration:2.0]]]]];
This is an example of how you can chain actions together inline if you’d like, instead of creating separate variables for each action.
这是一个使用单行代码的例子,如果你喜欢,可以不需要单独声明变量而使用这种形式.
Remove from parent action
用于移出父节点的动作
If you keep the game running for a while, you’ll notice that the node count in the bottom right keeps increasing.
如果你让游戏持续运行一段时间,你会注意到右下角的节点数在不断增加.
A never-ending list of nodes in a game is not a good thing. This node army will eventually consume all of the memory on the device, and the OS will automatically terminate your app, which from a user’s perspective will look like your app crashed.
一个持续增加的节点列表对于游戏来说不是什么好事.这些节点大军最终将导致设备的内存耗尽,然后OS就会自动的终止掉你的程序,从用户的角度来看,你的程序就这样崩了...
And as you may have guessed, there’s an action for that, too! When you no longer need a node and can remove it from the scene, you can either call removeFromParent directly, or use the remove from parent action.
可能大家已经猜到了,我们也有用来完成这个动作的action!当你不再需要一个今儿点的时候,你可以把它从场景中移除,也可以直接调用removeFromParent,又或者使用移除动作.
Let’s give this a try. Modify your list of actions in spawnEnemy: like so
一起来试一下.修改一下spawnEnemy:
SKAction *actionMove = [SKAction moveToX:-enemy.size.width/2 duration:2.0];
SKAction *actionRemove = [SKAction removeFromParent];
[enemy runAction:[SKAction sequence:@[actionMove, actionRemove]]];
And now the node count should always stay about constant, regardless of how long you run the game. Ah – much better!
现在无论运行多长时间,节点数量将会始终保持一个常量,世界美好了~
Animation action
动画动作
This one is super-useful, because adding animations is a super easy way to add a lot of polish and fun to your game.
这东西非常有用,因为添加动画是让游戏变得光鲜有趣的超级简单的办法.
To run an animation action, you first need to gather a list of images called textures that make up the frames of the animation. A sprite has a texture assigned to it, but you can always swap out the texture with a different one at runtime by setting the texture property on the sprite.
想要使用动画首先需要有一帧一帧的连续图片,我们称之为材质.一个sprite开始就会被指派一个材质,但是你可以通过修改texture属性来动态的修改sprite的材质.
In fact, this is what animations do for you – automatically swap out your sprite’s textures over time, with a slight delay between each.
事实上,这正是动画所做的事 — 没隔一段时间切换一张材质图片.
Zombie Conga already includes some animation frames for the zombie. you have four textures to use as frames to show the zombie walking.
项目中已经包含了一些僵尸的动画材质,你有4张材质图片来显示僵尸的移动.(Zombie1~4)
You can then repeat this endlessly for a continuous walk animation.
接下来可以无线循环这些图片来实现一个连续的行走动画.
Give it a shot. First create a private instance variable for the zombie action:
试一下,第一步为僵尸的动作创建一个私有成员:
SKAction *_zombieAnimation;
Then add the following code to initWithSize:, right after adding the zombie as a child to the scene:
然后在initWithSize:中添加僵尸节点的代码下方添加下列代码:
// 1
NSMutableArray *textures = [NSMutableArray arrayWithCapacity:10];
// 2
for (int i = 1; i < 4; i++) {
NSString *textureName = [NSString stringWithFormat:@"zombie%d", i];
SKTexture *texture = [SKTexture textureWithImageNamed:textureName];
[textures addObject:texture];
}
// 3
for (int i = 4; i > 1; i--) {
NSString *textureName = [NSString stringWithFormat:@"zombie%d", i];
SKTexture *texture = [SKTexture textureWithImageNamed:textureName];
[textures addObject:texture];
}
// 4
_zombieAnimation = [SKAction animateWithTextures:textures timePerFrame:0.1];
// 5
[_zombie runAction: [SKAction repeatActionForever:_zombieAnimation]];
2.The animation frames are named zombie1.png, zombie2.png, zombie3.png, and zombie4.png. This makes it nice and easy to create a loop that creates a string for each image name, and then makes a texture object from each name using SKTexture’s textureWithImageNamed: constructor.
动画材质的名字是zombie1~4.png.这种命名方式使得创建材质循环变得更加容易,然后通过SKTexture的textureWithImageNamed:方法来创建材质对象.
3. The first for loop adds frames 1 to 3, which is most of the “forward walk.” This for loop reverses the order and adds frames 4 to 2 to create the “backward walk” animation.
第一个循环添加了第1~3帧来创建”前进”的部分.这个循环则逆序添加了4~2来创建”后退”的动画.
Stopping action
停止动作
Your zombie’s off to a good start, but one annoying thing is that when the zombie stops moving, his animation keeps running. Ideally, you’d like to stop the animation when the zombie stops moving.
你的僵尸有了个不错的开始,但是有个讨厌的事 — 当僵尸的位置不在改变时依旧还在走太空步,真当自己是杰克逊了...我们需要的是当僵尸停止移动的时候动画也应该对应的停止.
In Sprite Kit, whenever you run an action, you can give the action a key simply by using a variant of the runAction: method called runAction:withKey:. This is handy because then you can stop the action later by calling removeActionForKey:.
在Sprite Kit中,无论你何时想用一个动作,你都可以用runAction:方法的变种runAction:withKey:来给动画一个key.这样就可以通过removeActionForKey:方法来停止这个动作.
Give it a shot by adding these two new methods:
试着添加这样两个方法:
- (void)startZombieAnimation {
if (![_zombie actionForKey:@"animation"])
{
[_zombie runAction: [SKAction repeatActionForever:_zombieAnimation] withKey:@"animation"];
}
}
- (void)stopZombieAnimation {
[_zombie removeActionForKey:@"animation"];
}
Now, go to initWithSize: and comment out the line that ran the action there:
现在,注释掉initWithSize:方法中运行action的方法:
//[_zombie runAction:
// [SKAction repeatActionForever:_zombieAnimation]];
Call startZombieAnimation at the beginning of moveZombieToward::
调用startZombieAnimation来让僵尸开始动画:
[self startZombieAnimation];
And call stopZombieAnimation inside update:, right after the line of code that sets _velocity = CGPointZero:
然后在update:方法中_velocity=CGPointZero;这行代码的下面调用stopZombieAnimation方法.
and now your zombie will only move when he should!
现在,你的僵尸只会在需要的时候移动了.