22
3 Cocos2D 动画和动作概述 在第 2 章中,我们学习了 Cocos2D 的基础知识,并创建了含有 2 个层的场景 :背景层 和游戏操作层。现在要使海盗精灵可以在屏幕上移动,这很可能使你有欲望继续完成功能 完整的游戏。在本章中将学习动作和动画,并在此过程中为 Space Viking 创建弹性框架。 在阅读本章时,记住在未来创建自己的游戏时你可以复用这个框架。 先提醒一下,本章和下一章有许多代码,但是不要恐惧,我们会一步步通过这个过程, 期间会解释一切。游戏开发覆盖计算机科学(缩写 :CompSci)的全部范围。在开发 Space Viking 过程中你将学习计算机科学的不同方面。请时时回来参考本章,以保证所有概念都 牢牢地盘踞在你的头脑中。 准备好了吗?我们开始吧! 3.1 Cocos2D 中的动画 使用 Cocos2D 在游戏中添加动画是很容易的。你已经学习了如何创建和添加精灵到层 中,并在屏幕上渲染所有精灵。动画在 Cocos2D 中与老式翻书页风格的动画非常相似 :所 有动画都只是在每秒钟被多次切换的独立图像帧。从技术角度来说,这意味着精灵会让显 示帧变更为不同的纹理,在每个纹理显示出来的时间之间有一些延迟。 使精灵变成动画需要两步: 创建一个 CCAnimation 对象,在动画中指定“图像 / 纹理”集合,称为帧。 创建一个 CCAnimate 动作对象,在一个精灵上“运行”它。CCAnimate 动作对象指 定使用哪个 CCAnimate,以及在帧之间使用多长时间的延迟。 回顾一下,你第一次创建 CCAnimation,用来在动画序列中保存帧或者图像。然后创 建一个 CCAnimate 动作去运行动画。 尝试两个快速示例是了解这种机制如何工作的简单方式。不用担心,第一个示例非常 短,只有几行代码。 1. Xcode 中打开 SpaceViking 项目。 2. 拖动本章 Resources/Enemy Robot Animation 文件夹中的 an1_anim_1234 png 文件到 SpaceViking 项目中。 an1_anim_1.png an1_anim_4.png 将使用在这个动画例子中。本章结束后,可以删除 它们,你将学习到使用第 2 章中创建的纹理图集驱动动画。 3. 打开 GameplayLayer.m 类,找到 init 方法。 4. 添加代码清单 3.1 所示代码行到 init 方法中,位置是在 [selfinitJoystickAndButtons]

Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

  • Upload
    others

  • View
    5

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章

Cocos2D 动画和动作概述

在第 2 章中,我们学习了 Cocos2D 的基础知识,并创建了含有 2 个层的场景 :背景层

和游戏操作层。现在要使海盗精灵可以在屏幕上移动,这很可能使你有欲望继续完成功能

完整的游戏。在本章中将学习动作和动画,并在此过程中为 Space Viking 创建弹性框架。

在阅读本章时,记住在未来创建自己的游戏时你可以复用这个框架。

先提醒一下,本章和下一章有许多代码,但是不要恐惧,我们会一步步通过这个过程,

期间会解释一切。游戏开发覆盖计算机科学(缩写 :CompSci)的全部范围。在开发 Space Viking 过程中你将学习计算机科学的不同方面。请时时回来参考本章,以保证所有概念都

牢牢地盘踞在你的头脑中。

准备好了吗?我们开始吧!

3.1 Cocos2D 中的动画

使用 Cocos2D 在游戏中添加动画是很容易的。你已经学习了如何创建和添加精灵到层

中,并在屏幕上渲染所有精灵。动画在 Cocos2D 中与老式翻书页风格的动画非常相似 :所

有动画都只是在每秒钟被多次切换的独立图像帧。从技术角度来说,这意味着精灵会让显

示帧变更为不同的纹理,在每个纹理显示出来的时间之间有一些延迟。

使精灵变成动画需要两步:

• 创建一个 CCAnimation 对象,在动画中指定“图像 / 纹理”集合,称为帧。

• 创建一个 CCAnimate 动作对象,在一个精灵上“运行”它。CCAnimate 动作对象指

定使用哪个 CCAnimate,以及在帧之间使用多长时间的延迟。

回顾一下,你第一次创建 CCAnimation,用来在动画序列中保存帧或者图像。然后创

建一个 CCAnimate 动作去运行动画。

尝试两个快速示例是了解这种机制如何工作的简单方式。不用担心,第一个示例非常

短,只有几行代码。

1. 在 Xcode 中打开 SpaceViking 项目。

2. 拖动本章 Resources/Enemy Robot Animation 文件夹中的 an1_anim_1、2、3、4 png文件到 SpaceViking 项目中。

an1_anim_1.png ~ an1_anim_4.png 将使用在这个动画例子中。本章结束后,可以删除

它们,你将学习到使用第 2 章中创建的纹理图集驱动动画。

3. 打开 GameplayLayer.m 类,找到 init 方法。

4. 添加代码清单 3.1 所示代码行到 init 方法中,位置是在 [selfinitJoystickAndButtons]

Page 2: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 46  第一部分 Cocos2D 初接触

之上。

代码清单 3.1 添加动画到 Space Viking 项目中

Chapter 3 Introduction to Cocos2D Animations and Actions58

To recap, you first create a CCAnimation to store the frames or images in your animation. You then create a CCAnimate action to run the animation.

The easiest way to understand how this works is to try out two quick examples. Don’t worry, the first example is short, just a few lines of code.

1. Open the SpaceViking project in Xcode.

2. Drag the an1_anim_1, 2, 3, and 4 png files from the Resources/Enemy Robot Animation folder for this chapter into your SpaceViking project.

The an1_anim_1.png through an1_anim_4.png are used in this animation example. After this chapter, you can remove them, as you will learn to drive the anima-tions from the texture atlas you created in Chapter 2.

3. Open the GameplayLayer.m class and navigate to the init method.

4. Add the lines shown in Listing 3.1 to the init method above the line [self initJoystickAndButtons];.

Listing 3.1 Adding an animation to Space Viking

// Animation example with a Sprite (not a CCSpriteBatchNode)

CCSprite *animatingRobot = [CCSprite

spriteWithFile:@"an1_anim1.png"]; // 1

[animatingRobot setPosition:ccp([vikingSprite position].x + 50.0f,

[vikingSprite position].y)]; // 2

[self addChild:animatingRobot]; // 3

CCAnimation *robotAnim = [CCAnimation animation]; // 4

[robotAnim addFrameWithFilename:@"an1_anim2.png"]; // 5

[robotAnim addFrameWithFilename:@"an1_anim3.png"];

[robotAnim addFrameWithFilename:@"an1_anim4.png"];

id robotAnimationAction =

[CCAnimate actionWithDuration:0.5f

animation:robotAnim

restoreOriginalFrame:YES]; // 6

id repeatRobotAnimation =

[CCRepeatForever actionWithAction:robotAnimationAction]; // 7

[animatingRobot runAction:repeatRobotAnimation]; // 8

In Listing 3.1 you are adding an animating enemy robot to the right of Ole. Here is what is going on in the code step by step:

1. Creates and initializes a CCSprite of the animating robot, with the image an1_anim1.png.

2. Sets the position of the robot to 50 points to the right of Ole. If you wanted the exact pixel position of the Viking, you would use the call to positionInPixelsinstead of position.

在代码清单 3.1 中,你在 Ole 右侧添加了一个带动作的敌对机器人。这里介绍了代码的

每一步到底做了什么:

1. 用图像 an1_anim1.png,创建并初始化一个带动作的机器人 CCSprite 对象。

2. 设置机器人位置在 Ole 右侧 50 个点。如果你想得到精确的海盗像素位置,可以使用

对 positionInPixels 的调用代替 position。3. 添加 animatingRobot 到 GameplayLayer 中。此时,创建和添加 CCSprite 到 CCLayer

的步骤,与在上一章学习的非常相似。

4. 创建并初始化一个新的 CCAnimation 对象,命名为 robotAnim。到这一行 robotAnim创建完成但只是个空对象。

5. 为 robotAnim 对象添加动画帧。每个被传入的文件名生成三个帧。

6. 创建一个 robotAnimationAction 对象,用来负责运行动画。帧之间的延迟被设置为半

秒(0.5f),使用的动画设置为 robotAnim,标志设置为恢复原始帧。“恢复原始帧”标志通

知 CCAnimate 动作在动画结束后做一些工作,使 CCSprite 对象显示在动画开始之前显示的

帧。换而言之,当动画结束,CCSprite 对象转换回原始图像。2

7. 创建一个动作一直重复之前的动画。在第 4 章中,你将学习如何使用动作在 Space Viking 中驱动行为。现在,只需要知道 CCRepeatForever 动作对象让机器人动画不断重复即可。

8. 通知 animatingRobot CCSprite 无限循环执行动画。

 即,回到第一次加载的图像。—译者注

Page 3: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  47 

在 Xcode 中点击 Run,可看到机器人在维京海盗附近走动。图 3.1 展示了在 iPad 模拟

器中的机器人。

Animations in Cocos2D 59

3. Adds the animatingRobot to the GameplayLayer. Up to this point the steps in creating and adding a CCSprite to a CCLayer should be familiar, as you learned it in the previous chapter.

4. Creates and initializes a new CCAnimation named robotAnim. In this line the robotAnim is created but empty.

5. Adds the animation frames to the robotAnim animation. Three frames are added by passing in the filenames for each.

6. Creates the robotAnimationAction that is responsible for running the anima-tion. The delay between frames is set to half a second (0.5f), the animation to use is set to robotAnim, and the f lag is set to restore the original frame. The restore original frame f lag informs the CCAnimate action to do a bit of work at the end of the animation and reset the CCSprite to the display frame it was showing before the animation started. In other words, when the animation ends, the CCSprite reverts back to its original image.

7. Creates an action to repeat the animation forever. In Chapter 4, “Simple Colli-sion Detection and the First Enemy,” you will learn how to use actions to drive behavior in Space Viking. For now, just know that the CCRepeatForever action keeps repeating the robot animation.

8. Informs the animatingRobot CCSprite to run the animation in an endless loop.

Click Run in Xcode and watch the robot walk in place next to the Viking. Figure 3.1 shows the robot on the iPad Simulator.

Figure 3.1 Animating robot next to Ole

图 3.1 Ole 旁边有动作的机器人

如同你所看到的,在 Cocos2D 中创建动画是非常简单的。代码清单 3.1 中有一个常规

的 CCSprites,但是如果 CCSprites 是由 CCSpriteBatchNode 渲染的,代码需要做大量的修

改。在动画中使用的 CCSpriteFrames 帧,必须是 CCSpriteBatchNode 使用的纹理图集中的

纹理。了解这个机制的最佳方式是查看动作内部。直接拷贝代码清单 3.2 中的代码,到之前

创建的代码清单 3.1 代码的下面。

代码清单 3.2 使用由 CCSpriteBatchNode 渲染的 CCSprites 的动画

Chapter 3 Introduction to Cocos2D Animations and Actions60

As you can see, creating animations in Cocos2D is easy. The code in Listing 3.1 works with regular CCSprites, but if your CCSprites are being rendered by a CCSpriteBatchNode, the code requires a couple of modifications. The CCSprite-Frames you use for your animation must get their textures from the texture atlas used by the CCSpriteBatchNode. The best way to understand this is to see it in action. Copy the contents of Listing 3.2 directly below the code you created earlier in Listing 3.1.

Listing 3.2 Animations using a CCSprite rendered by CCSpriteBatchNode

// Animation example with a CCSpriteBatchNode

CCAnimation *exampleAnim = [CCAnimation animation];

[exampleAnim addFrame:

[[CCSpriteFrameCache sharedSpriteFrameCache]

spriteFrameByName:@"sv_anim_2.png"]];

[exampleAnim addFrame:

[[CCSpriteFrameCache sharedSpriteFrameCache]

spriteFrameByName:@"sv_anim_3.png"]];

[exampleAnim addFrame:

[[CCSpriteFrameCache sharedSpriteFrameCache]

spriteFrameByName:@"sv_anim_4.png"]];

id animateAction =

[CCAnimate actionWithDuration:0.5f

animation:exampleAnim

restoreOriginalFrame:NO];

id repeatAction =

[CCRepeatForever actionWithAction:animateAction];

[vikingSprite runAction:repeatAction];

Recall from Chapter 2 that you used TexturePacker or Zwoptex to create a big sprite sheet image with all of the images for Space Viking, and a property list contain-ing the coordinates for each image in the sprite sheet. You then added code to use the addSpriteFramesWithFile method to load the property list, which makes Cocos2D cache the coordinates for each individual image so you can look them up later. Looking them up is simple—you just use the spriteFrameByName method, which returns a CCSpriteFrame object containing the location information.

To set up a CCAnimation, you need to specify each CCSpriteFrame in the ani-mation. So in Listing 3.2, the only difference is that addFrame is used instead of addFrameWithFilename. This way, you can specify the CCSpriteFrames that are already in the cache from the sprite sheet’s property list.

Page 4: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 48  第一部分 Cocos2D 初接触

回忆第 2 章中,我们使用 TexturePacker 或 Zwoptex 工具创建了一个巨大的包含 Space Viking 游戏中使用的所有图像的精灵图像表格,以及包含在精灵表格中的每一个图像坐标

的属性列表。然后为 addSpriteFramesWithFile 方法添加代码去加载属性列表,属性列表可

以让 Cocos2D 缓存每一个独立图像的坐标,这样就可以在稍后找到它们。找到那些图是很

简单的—只需要使用 spriteFrameByName 方法,它会返回包含位置信息的 CCSpriteFrame对象。

要设置一个 CCAnimation 对象,需要指定动画中的每一个 CCSpriteFrame。所以在代码

清单 3.2 中,唯一的不同是使用 addFrame 方法替代方法 addFrameWithFilename。这样,就

可以指定已经缓存的精灵表格的属性列表 CCSpriteFrames。一旦 CCAnimation 对象填充完毕,余下的代码都是相同的,设置一个动作去运转一个

动画,同时将它分配给 vikingSprite。这里最重要的事情是,当使用纹理图集创建一个精灵的动画时,动画帧必须来自相同

的纹理图集。如果希望得到一个精灵动画,确保把所有动画相关的帧放到同一个精灵表格

里—否则动画不会正常工作。

关于敌人机器人尺寸的提示

如果在非 iPad 设备上运行代码清单 3.1 所示代码,你会收到一个提示,说 EnemyRobot的尺寸过大,然后是海盗的。在本章中键入的第一段代码是纯用于 iPad 尺寸的 CCSprites,没有使用 UI_USER_INTERFACE_IDIOM() 方法来确定应用是运行在 iPhone 还是 iPad 上。

所有用 Space Viking 写的后续代码,都会使用在第 2 章创建的纹理图集,并将依据游戏运

行的硬件平台(iPhone、iPhone4 或 iPad)选择正确的纹理图集。

在 Space Viking 游戏中,帧率(fps)被设置为 60 帧每秒。通过改变帧对象间的延迟,

可以控制每一帧在屏幕上驻留的时长。如果需要保持一组帧对象在屏幕上显示的时间长于

其他帧对象,可以在 CCAnimation 对象中多次添加这些帧。记住,Cocos2D 为每一个帧对

象缓存纹理;重复一个帧不会消耗多少额外的内存。

警告 代码清单 3.2 中的代码假设你完成了前 1 章中设置 CCSpriteBatchNode 对象的所有步

骤。如果遇到任何问题,记得参考本章中附带的示例代码。

如果你尚未完全理解这一切,不要着急 :在本章的代码清单中,你将有机会看到这一

切是如何联系在一起的。只要记住,要播放一个动画,需要使用 CCAnimate 动作,你总是

可以参考使用这个示例的代码。34

Space Viking 中有许多角色,每个都有一系列动画。键入每一个动画的每一帧会很快变

得乏味,一行又一行的 addFrame 方法调用只会让代码体积越来越大。每一个 CCAnimation和 CCAnimate 动作,有两个组成部分 :一个帧间延迟 和一组精灵帧的过程序列 。这种动

画数据最好保存在与代码独立的属性列表(plist)文件中。本章包括 plist 文件和设置 Space

 可以换算成帧速率。—译者注

 例如一个完整的向前步行的 6 帧分镜头。—译者注

Page 5: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  49 

Viking 中的角色动画的代码。

此刻你已经取得了相当一些进展。你已经开始进行 Cocos2D 游戏开发,并能运用游戏

操纵杆在屏幕上驱动角色。Space Viking 中的动画将用来给角色生命,使角色行走、跳跃、

蹲下和攻击。本章中的其余部分将基于你之前学习到的 Space Viking 基础,包含很大一部

分你可以在自己的游戏中复用的代码。

注意 在继续开始之前,确保已经注释掉在代码清单 3.1 和代码清单 3.2 中添加的代码行 :

这些代码只是示例不能用于本书其余部分。

为更好奇的读者:CCAnimationCacheCocos2D 捆绑了一个 CCAnimationCache 单例对象,这个对象可以缓存所有的动画。这

里不是把动画作为实例变量存储,你可以在 CCAnimationCache 中存储它们,并根据需要检

索。如果游戏中有许多 CCSprites 使用相同的动画,这会非常有用。一个很贴切的例子是有

相似敌人的射击类游戏,如经典的 Space Invaders 或 R-Type。当使用 CCAnimationCache 时有两项需要关注:

1. 当检索动画时,需要检查它是否不是 nil。通过 purgeSharedAnimationCache 调用,

CCAnimationCache 可以清除动画。

如果你请求的动画序列不再被缓存,调用会返回 nil。2. 任何你想保留的动画会被保留,所以如果它们被从缓存中清除,对于你的对象它们

仍然可用。

使用 CCAnimationCache 对象是很简单的。如本章代码清单一样创建动画,并添加它们

到 CCAnimationCache 对象中,如下:

Chapter 3 Introduction to Cocos2D Animations and Actions62

NoteBefore moving on, be sure to comment out the lines of code you added from Listings 3.1 and 3.2: they are examples only and are not used in the rest of the book.

For the More Curious: CCAnimationCacheCocos2D comes bundled with a CCAnimationCache singleton that can cache all of your animations. Instead of storing the animations as instance variables, you can store them in the CCAnimationCache and retrieve them as needed. This is really useful if you have a lot of CCSprites in your game all using the same animations. A good example is a shooting game with similar enemies, such as the classic Space Invaders or R-Type.

There are two items you should be aware of when using the CCAnimationCache:

1. When retrieving the animation, you should check that it is not nil. The CCAnimationCache can purge animations if asked via the purgeShared-AnimationCache call. If the animation you ask is no longer cached, it will return nil.

2. Any animations you wish to keep around should be retained so that if they are purged from the cache, they will still be available to your objects.

Using CCAnimationCache is really easy. You create the animation as you did in the listings in this chapter and then add them to the CCAnimationCache as follows:

[[CCAnimationCache sharedAnimationCache]

addAnimation:animationToCache

name:@"AnimationName"];

The CCAnimation called animationToCache is now stored in the CCAnimation-Cache singleton. When you want to use the cached animation, you can retrieve it by using:

CCAnimation *myAnimation = [[CCAnimationCache sharedAnimationCache]

animationByName:@"AnimationName"];

In SpaceViking the choice was made to store the animations as instance variables because it was deemed easier to understand and use. In your games do not hesitate to use the CCAnimationCache, especially in cases where you have several objects using the same animations.

Space Viking Design BasicsIn Chapter 2 you had a very simple game with a Viking character and joystick controls to move him around. You could continue building a game this way, but the Gameplaylayer would grow very large and quickly become unmanageable. The goal of this book is to provide you with the knowledge and skills needed for game development with

命名为 animationToCache 的 CCAnimation 对象现在存储在 CCAnimationCache 单例中。

当你想使用缓存的动画时,可以用下面的代码检索:

Chapter 3 Introduction to Cocos2D Animations and Actions62

NoteBefore moving on, be sure to comment out the lines of code you added from Listings 3.1 and 3.2: they are examples only and are not used in the rest of the book.

For the More Curious: CCAnimationCacheCocos2D comes bundled with a CCAnimationCache singleton that can cache all of your animations. Instead of storing the animations as instance variables, you can store them in the CCAnimationCache and retrieve them as needed. This is really useful if you have a lot of CCSprites in your game all using the same animations. A good example is a shooting game with similar enemies, such as the classic Space Invaders or R-Type.

There are two items you should be aware of when using the CCAnimationCache:

1. When retrieving the animation, you should check that it is not nil. The CCAnimationCache can purge animations if asked via the purgeShared-AnimationCache call. If the animation you ask is no longer cached, it will return nil.

2. Any animations you wish to keep around should be retained so that if they are purged from the cache, they will still be available to your objects.

Using CCAnimationCache is really easy. You create the animation as you did in the listings in this chapter and then add them to the CCAnimationCache as follows:

[[CCAnimationCache sharedAnimationCache]

addAnimation:animationToCache

name:@"AnimationName"];

The CCAnimation called animationToCache is now stored in the CCAnimation-Cache singleton. When you want to use the cached animation, you can retrieve it by using:

CCAnimation *myAnimation = [[CCAnimationCache sharedAnimationCache]

animationByName:@"AnimationName"];

In SpaceViking the choice was made to store the animations as instance variables because it was deemed easier to understand and use. In your games do not hesitate to use the CCAnimationCache, especially in cases where you have several objects using the same animations.

Space Viking Design BasicsIn Chapter 2 you had a very simple game with a Viking character and joystick controls to move him around. You could continue building a game this way, but the Gameplaylayer would grow very large and quickly become unmanageable. The goal of this book is to provide you with the knowledge and skills needed for game development with

在 Space Viking 中,用实例变量存储动画,因为这样更容易理解和使用。在游戏中不

要犹豫使用 CCAnimationCache,尤其是在有几个对象使用相同动画的情形下。

3.2 Space Viking 设计基础

在第 2 章中,我们有一个简单的游戏,其中有一个维京海盗,我们用游戏操纵杆控制

他来回移动。你可以用这种方式继续构建游戏,但是 Gameplay 层会变得非常巨大,很快会

变得难以处理。本书的目的是给你提供知识和必要的技能使用 Cocos2D 进行游戏开发,并

提供一个可以在你的游戏中复用的带源码的简单游戏。

Page 6: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 50  第一部分 Cocos2D 初接触

Space Viking 中每个游戏对象背后都有一些逻辑,而不仅仅只是图形化的精灵 / 图片组

件。敌人需要一些简单的人工智能(AI),使其可以在场景中来回巡视,攻击 Ole 和对攻击

做出反应。除了敌人外,还有更好的武器、生命指数道具 和其他道具。这里没有把所有逻

辑都放在一个巨大的文件中,而是游戏中的每个对象都有其自己的类,类中包含必要的代

码部分。游戏逻辑可由多个对象使用,但只在类中出现一次,这些对象都继承自该类,如

GameObject 或 GameCharacter。5

思考与对象有关的术语

从本章开始,你将通过分离每个角色或游戏元素的功能来为 Space Viking 实现逻辑。

每个对象用 Object-C 语言的一个类实现,其中包括一个头文件和一个实现文件。

思考类或面向对象编程如何工作的一种方式是,为常见对象建模并了解对象 / 类结构是

如何工作的。借用一个狗的例子,如金毛猎犬(俗称“金毛”)。

在对象设计中,你总是希望从抽象到具体。在金毛猎犬这个案例中,你可能从一个

Mammal(哺乳动物)对象开始。

Mammal 对象包含所有动物的基本特征,如神经系统组件、骨骼系统组件和肌肉系

统组件。从 Mammal 对象开始,你可以有一个 Canine(犬)对象,这是一个更为具体的

Mammal 对象。Canine 对象有四条腿、有吠叫的能力等所有狗的特征。最后,你要有一个

Golden-Retriever(金毛猎犬)对象,这将是更为具体的 Canine 的类型,其中包含 Golden-Retriever(金毛猎犬)的特征。

当把对象转换成类时,Mammal 对象是一个类,Canine 对象是 Mammal 对象的一个子类,

Golden-Retriever 对象是 Canine 对象的一个子类。每个对象组件可以是实例变量(ivar),或者如果对象有复杂的功能,也可以是对象本身。皮毛的颜色可以存储在 ivar 变量中,但

是骨骼系统组件可能由许多部分构成,以建模骨骼和关节的复杂功能。

当为游戏设计对象和类的时候,切记从抽象到具体,更为抽象的类在继承关系的顶部。

对象和类的共同方法,应该移到父类对象或更高一级的层次关系中。如果有一个方法两个

类都需要,可以考虑一下,把这个方法放在父对象中是不是会更好。如果你需要复习一下

面向对象编程的知识,可参考一下经典的《Object-Oriented Programming》,作者是 Peter Coad 和 Jill Nicola(Prentice-Hall, 1993)。

在 Space Viking 中每个对象都有自己的类,其中封装了对象需要的人工智能。对象

间共享通用的方法,可以用来相互查询彼此的状态、当前的位置和其他的属性。要了解

所有这些对象是如何结合在一起的,最好是在层次结构中最顶层的对象开始,也就是从

GameObject 开始。

• GameObjectGameObject 类继承自 CCSprite 类,包含所有 GameObject 通用的方法,如在每个帧更

新对象的方法和改变对象状态的方法。因为 GameObject 本身继承自 CCSprite,它已经知道

 health power-ups,角色或者敌人的“血量”,这里译为“生命指数道具”。—译者注

Page 7: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  51 

如何在屏幕上渲染纹理。请记住,CCSprites 可以渲染自身或者被一个 CCSpriteBatchNode所渲染。Space Viking 中的所有对象都直接或间接继承自 GameObject 类。

• GameCharacterGameCharacter 类继承自 GameObject 类,是游戏中各种“角色(character)”的基

础。它包含一些其他方法和 Space Viking 中的角色需要的功能。Viking(维京海盗角色)、

EnemyRobot(敌人机器人角色)、RadarDish(镭射天线)和 PhaserBullet(激光枪)都继承

自 GameCharacter。• VikingViking 类继承自 GameCharacter 类,包含逻辑、匹配游戏操纵杆输入来回移动 Ole、发

动攻击并对来自敌人的攻击做出反应所需的代码。

• RadarDishRadarDish 是一个不能移动的敌人,是它把 Ole 带到了外星。它在角落里,扫描我们的

Viking(维京海盗角色),直到被他察觉。我们认为镭射天线是一个好对付的不会还击的敌

人。了解 RadarDish 的工作原理是非常重要的,因为 Space Viking 中的其他敌人是镭射天线

的更复杂版本。RadarDish 类继承自 GameCharacter 类。

• SpaceCargoShip外星世界是一个巨大的矿物星球。外星人一直运送货物进入和离开星球。当外星飞船

接近地面时,有些物体会降落到地面。这些物体中的一些可能对维京海盗 Ole 有用吗?

SpaceCargoShip 类继承自 GameObject 类。

• Mallet很久以前,当 Ole 坠入冰河时,与他心爱的锤子分离了。如果再次发现它,他将被一

个强大的武器武装起来对抗外星人。Mallet 类继承自 GameObject 类,在 Space Viking 的第

一个场景中,是众多道具中的一个。

• Health在 Space Viking 中,Health 类描绘了生命指数道具。尽管 Ole 是一个强大的维京海盗,

他渴望战斗后,生命道具能恢复他的能量值 Health 类继承自 GameObject 类。

注意 在本章中,你将为 Space Viking 创建这些类。在第 4 章你将添加敌对机器人和机器

人射向 Ole 的激光束。Cocos2D 中的动作和动画系统被 Space Viking 中的 GameObject 所广

泛使用,在下一节会详细介绍。

图 3.2 展示了 Viking 类和 RadarDish 类的继承关系和组成,以及它们如何添加到场

景 1 CCSpriteBatchNode 中,CCSpriteBatchNode 转而被添加到 GameplayLayer 中。回想

一下在 Cocos2D 中,CCScenes 包含 CCLayers、CCLayers 包含对象,在我们的案例中,

CCSpriteBatchNode 中包含 Viking 类和 RadarDish 类。

Page 8: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 52  第一部分 Cocos2D 初接触

Space Viking Design Basics 65

n Health

The Health class represents the health power-ups in Space Viking. Even though Ole is a mighty Viking, he does get hungry after fighting, and the health power-ups restore Ole’s health. The Health class inherits from the GameObject class.

NoteYou will create these classes for Space Viking in this chapter. In Chapter 4, you will add the enemy robot and phaser beams that the robot fires at Ole. The actions and animation system in Cocos2D is used extensively by the GameObjects in Space Viking, and it is covered in detail in the next section.

Figure 3.2 shows the inheritance and makeup of the Viking and RadarDishclasses and how they are added to the scene 1 CCSpriteBatchNode, which in turn is added to the GameplayLayer. Recall that in Cocos2D, the CCScenes contain CCLayers, the CCLayers contain objects, in our case the CCSpriteBatchNode,which then contains the Viking and RadarDish classes.

Figure 3.2 Class hierarchy of the Viking and RadarDish classes

A Word on Artificial IntelligenceDepending on the type of game you are creating, the demands of the in-game AI will vastly differ. If you were making a racing game, the enemy cars only have to make their way around the track and avoid each other, while a real-time strategy game would require a more complex AI. In many games the AI is represented as state machines, with distinct operations at each state. In Space Viking the enemy AI is a very simplis-tic state machine, going between walking, attacking, and taking damage states. The complexity of the AI in your games is limited only by your imagination and how much computing time you want to devote to it in your game.

图 3.2 Viking 类和 RadarDish 类的继承关系

关于人工智能的一些建议

取决于创建游戏的类型,游戏中对人工智能(Artificial Intelligence, AI)的要求有很大

区别。如果你制作一款赛车游戏,敌对车辆只需要在赛道上行驶,避开对方,而实时战略

游戏则需要更复杂的 AI。在许多游戏中 AI 表现为状态机,每个状态会有不同的反应。在

Space Viking 中敌人的 AI 是非常简单的状态机,分别是行走、攻击和损伤状态。游戏中 AI的复杂性受限于你的想象力和你想在游戏中投入多少计算时间。

3.3 Cocos2D 动作和动画基础

在 Cocos2D 中,动作是一个控制对象移动、变换和特效的强有力机制。所有 CCNode对象可以运行动作,因为 CCSprites 继承自 CCNodes,所有 Cocos2D 的动作都可以在

CCSprites 上 运 行。Viking 和 RadarDish 继 承 自 GameCharacter,GameCharacter 继 承 自

GameObject 和 CCSprite,这样的角色有内置的逻辑可以运行动作。在第 2 章中,我们通过

改变精灵坐标变量来移动 Ole 的精灵。假设你想在两秒钟内,在屏幕上从左至右移动 Ole 200 个像素。下面的移动动作将为你完成这个任务。

Chapter 3 Introduction to Cocos2D Animations and Actions66

Actions and Animation Basics in Cocos2DActions are a powerful mechanism in Cocos2D to control the movement, transi-tion, and effects of your objects. All CCNode objects are able to run actions, and since CCSprites inherit from CCNodes, all Cocos2D actions can be run on CCSprites.The Viking and RadarDish objects inherit from GameCharacter, which in turns inherits from GameObject and CCSprite, allowing your characters to have all of the built-in logic to run actions. In Chapter 2 you moved Ole the Viking’s sprite by changing the position variable of the sprite. Suppose you wanted to move Ole from the left to the right of the screen 200 pixels in 2 seconds. The following move action would accomplish this for you.

CCAction *moveAction = [CCMoveBy actionWithDuration:2.0f

position:ccp(200.0f,0.0f)];

[vikingSprite runAction:moveAction];

That’s all it takes to do the animation—just two lines of code! Figure 3.3 illustrates the moveAction effect on the Viking sprite.

Figure 3.3 Effect of the CCMoveBy action on the Viking CCSprite

You don’t have to worry about how many pixels you should move on each update call because Cocos2D automatically calculates and moves your sprite in the correct increments. There are many actions in Cocos2D: move, jump, scale, and rotate are just a few of the most frequently used ones. In Chapter 5, “More Actions, Effects, and Cocos2D’s Scheduler,” you will learn more details about actions, including complex callbacks into your code. For now you will use the following actions:

n CCAnimate

Runs a particular animation with a set delay between each frame. This action is what you use to play an animation.

n CCJumpBy

Simulates a parabolic jump automatically, given the height of the jump and the horizontal distance to cover. This action is used to give Ole the Viking a realistic jump arc.

这就是所有需要做的—只是两行代码!图 3.3 展示

了作用于维京海盗精灵的 moveAction 效果。

你不必担心每次更新调用时需要移动多少像素,因

为 Cocos2D 会自动计算,以正确的增量移动精灵。在

Cocos2D 中有许多动作 :移动、跳跃、缩放和旋转都只

是最常使用的。在第 5 章中,你将学习关于动作的更多细

Chapter 3 Introduction to Cocos2D Animations and Actions66

Actions and Animation Basics in Cocos2DActions are a powerful mechanism in Cocos2D to control the movement, transi-tion, and effects of your objects. All CCNode objects are able to run actions, and since CCSprites inherit from CCNodes, all Cocos2D actions can be run on CCSprites.The Viking and RadarDish objects inherit from GameCharacter, which in turns inherits from GameObject and CCSprite, allowing your characters to have all of the built-in logic to run actions. In Chapter 2 you moved Ole the Viking’s sprite by changing the position variable of the sprite. Suppose you wanted to move Ole from the left to the right of the screen 200 pixels in 2 seconds. The following move action would accomplish this for you.

CCAction *moveAction = [CCMoveBy actionWithDuration:2.0f

position:ccp(200.0f,0.0f)];

[vikingSprite runAction:moveAction];

That’s all it takes to do the animation—just two lines of code! Figure 3.3 illustrates the moveAction effect on the Viking sprite.

Figure 3.3 Effect of the CCMoveBy action on the Viking CCSprite

You don’t have to worry about how many pixels you should move on each update call because Cocos2D automatically calculates and moves your sprite in the correct increments. There are many actions in Cocos2D: move, jump, scale, and rotate are just a few of the most frequently used ones. In Chapter 5, “More Actions, Effects, and Cocos2D’s Scheduler,” you will learn more details about actions, including complex callbacks into your code. For now you will use the following actions:

n CCAnimate

Runs a particular animation with a set delay between each frame. This action is what you use to play an animation.

n CCJumpBy

Simulates a parabolic jump automatically, given the height of the jump and the horizontal distance to cover. This action is used to give Ole the Viking a realistic jump arc.

2秒种内移动200个像素

图 3.3 作用于维京海盗 CCSprite

的 CCMoveBy 效果

Page 9: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  53 

节,包括代码中的复杂回调。现在可以使用下面的动作:

• CCAnimate伴随每帧之间的延迟运行一个特定的动画。这个动作用来播放一个动画。 • CCJumpBy已知跳跃高度和水平距离跨度,自动模拟抛物线跳跃。这个动作用来给海盗 Ole 一个

真实的跳跃弧度。

• CCRepeatForever无限重复一个动作。它的效果是以无限循环运行一个动作,直到 stopAction 或

stopAllActions 被调用。

• CCSequence使两个或更多个动作连接在一起,第一个动作完成后第二个动作开始执行。

• CCSpawn同时连续不断地运行两个或更多个动作。CCSpawn 对于组合动作非常有用,例如一个

跳跃动画和一个 CCJumpBy 动作。

本章中将使用动作去驱动动画,包括 Ole 的跳跃和攻击。下一节介绍通过属性列表添

加动画。

3.4 使用属性列表文件存储动画数据

属性列表文件(通常用“pList files”来指代)包括用来定义一组属性的 XML 数据,

这些属性使用在应用或游戏中。在 Space Viking 中将使用属性列表文件来存储一个设置的

NSDictionary,这些设置是创建和加载 CCAnimations 时需要用到的。Xcode 内置支持创建

和编辑属性列表文件,这使得在你的项目中使用属性列表文件变得非常轻松。

当然,你也可以直接在代码中包含来自于属性列表文件中的所有信息,但是,每次需

要对动画做出修改,你不得不修改代码并重新编译。动画数据保存在属性列表文件中可以

使代码的规模相对更小,并允许你在艺术家的帮助下设置和调整动画帧和延迟。我已经发

现硬编码(hard-code)动画数据不是很好的做法。分离动画数据,有助于划分代码功能,

当你几个月后再次访问相同的代码时,任务会变得更简单。

在 Space Viking 中每个对象都有自己的类,例如 Viking 和 RadarDish。每个对象指向

它自己的属性列表文件,其中包括如何设置它的动画的信息。例如,在 RadarDish 示例中,

RadarDish.plist 文件包含用于 takingAHit、blowingUp、tilting 和 transmitting 动画的数据。

正如它的名字所暗示的那样,NSDictionary 对象是一组“键—值”(key-value)对。在

Space Viking 中,属性列表文件为每个动画包含一个带文件名前缀、一个延迟量和一系列动

画单帧列表的迷你数据字典(mini-dictionary)。动脑解决这个问题的最快方式是创建一个,

那么让我们开始行动。

从 Space Viking 中最简单的敌人 RadarDish 开始,打开 Xcode:1. 鼠标右键单击或按 Control 键单击 SpaceViking 项目,选择 New Group。

Page 10: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 54  第一部分 Cocos2D 初接触

2. 单击新建立的组,修改其名字为 Plists。你将使用这个组来组织用于 Space Viking 中

所有动画的属性文件。

3. 选择 Plists 组,鼠标右键单击或按 Control 键单击,选择 New File。4. 在左侧的 iOS 下选择 Resource 选项,选择文件类型为 Property List,点击 Next。5. 键入 RadarDish.plist 作为文件名,然后点击 Finish。Xcode 会为你创建并打开 RadarDish.plist 文件。下一步是为指定的 RadarDish 动画添加

必要的条目。图 3.4 展示了本节将要创建的 RadarDish.plist 文件。

Chapter 3 Introduction to Cocos2D Animations and Actions68

3. Selecting the Plists group, right- or Control-click and select New File.

4. Select the Resource section under iOS on the left, and then select Property List for the type of file and click Next.

5. Enter RadarDish.plist for the filename, and then click Finish.

Xcode creates and opens the RadarDish.plist file for you. The next step is to add the items needed for a particular RadarDish animation. Figure 3.4 shows the RadarDish.plist file you will create in this section.

Figure 3.4 RadarDish.plist file

The first animation is for the tilting movement that the RadarDish performs when it is first created. This animation consists of a series of frames showing the dish tilting down and then back up. With the RadarDish.plist file open in Xcode, do the following (the numbers in the list correspond to the numbered callouts in Figure 3.4):

1. Select the RadarDish.plist file and in the empty editor area, right-click, selecting Add Row.

2. For the Key column, enter tiltingAnim, then change the type to Diction-ary. This step means you have created a subdictionary inside the main root dictionary to store the data for the tilting animation. After you set the type to Dictionary, you’ll notice a disclosure triangle next to tiltingAnim in the Key column.

3. Click the disclosure triangle next to tiltingAnim so it faces down; this opens the dictionary so you can add items to tiltingAnim’s dictionary.

图 3.4 RadarDish.plist 文件

第一个动画是当 RadarDish 首次创建时完成的倾斜运动。动画包含一系列帧,用来展示

天线倾斜,然后倒退。随着 RadarDish.plist 在 Xcode 中打开,继续下面的步骤(图 3.4 中

的编号与列表中的号码编号对应)。

1. 选中 RadarDish.plist 文件,在空编辑区右击,选择 Add Row。

2. 在 Key(键)栏键入 tiltingAnim,然后改变其类型为 Dictionary。这一步意味着你已

经在主根数据字典内为保存倾斜动画数据创建了一个子数据字典。在设置类型为 Dictionary后,你会发现在“Key”栏中,tiltingAnim 旁边显示出了一个三角形。

3. 点击 tiltingAnim 旁边的三角形,会向下弹开 ;这个动作会打开数据字典,你可以向

tiltingAnim 的数据字典添加条目。

4. 点击 tiltingAnim 旁边的“+”按钮,向 tiltingAnim 的数据字典添加一行。

5. 点击“Key”字段,键入“animationFrames”。6. 令其类型为 String。7. 在“Value”字段,键入“1,2,3,4,5,5,4,3,2,1,2,3,4,5”。8. 点击 animationFrames 字段旁边的“+”符号,在 tiltingAnim 下面添加其他条目,设

置新行的键名为 delay,类型为 Number,值为 0.25。

Page 11: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  55 

9. 在 tiltingAnim 下面创建最后一个条目,设置键为 filenamePrefix,类型为 String,值

为 radar_。要验证你创建的条目确实在 tiltingAnim 之下,可点击 tiltingAnim 右侧打开的三角形,

使其折叠。然后所有三个项目应该从视图中消失。如果不是这样的话,在没有消失的条目

上右击,选择 shift item right。为 takingAHitAnim、blowingUpAnim 和 transmitting-Anim 的微型动画数据字典重复以

上步骤,键入如图 3.4 所示的值。

动画数据保存在属性列表文件中的其中一个关键性的好处是,它可以使你免于去代码

中修改动画数据。事实上,在游戏项目中,如果你与一个艺术家或动画师一起工作,他们

可以经常构建这些文件,犹如创建动画一样。本章稍后你将可以看到如何使用 GameObject类中的一个方法读取动画的属性列表文件,这个方法会被 Space Viking 中的每个对象调用。

注意 虽然最重要的是了解如何在 Space Viking 中使用属性列表文件,但是为了学习制作

游戏这个目的,不值得你费时间手工键入这些。所以在继续之前,在 InformIT 网站(www.informit.com/title/9780321735621)下载本章带源码的属性列表文件。

一旦下载源码后,添加属性列表文件到你项目的 Plists 组(EnemyRobot.plist、Health.plist、Mallet.plist、RadarDish.plist 和 Viking.plist)。

一定要记住添加这些文件到你的项目中,因为如果没有它们,代码的其余部分就不能

正常工作!

3.5 组织、常量和通用协议

后面几节将创建用于 Space Viking 的很多类。在每个后续的章节中,将添加更多的功

能和更多的类。为便于理解,最好现在开始在 Xcode 中组织源代码。

首先,在 Xcode 中创建一些组,以帮助存放将要组织创建的新类。

1. 打开 Xcode,在组和文件选项卡中,选中 Classes 文件夹。

2. 随着 Classes 文件夹被选中,你可以按快捷键 Option-z-N,或者在 Classes 文件夹上

右击,从上下文菜单中选中 New Group。创建如下组:

• Constants• Scenes• Layers• GameObjects• EnemyObjects• PowerUps• Singletons3. 拖动 GameScene.h 和 GameScene.m 文件到 Scenes 组。

4. 拖动 BackgroundLayer、GameplayLayer .h 和 .m 文件到 Layers 组。

Page 12: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 56  第一部分 Cocos2D 初接触

5. 最后,拖动 SpaceVikingAppDelegate .h 和 .m 文件到 Singletons 组。

此时,你的 Space Viking 项目的外观布局如图 3.5 所示,组的顺序并不重要,重要的是

在 Classes 文件夹下有众多组,用来组织你的代码。如果 HelloWorldScene.m 和 .h 文件仍然

在你的 Space Viking 项目中,现在可以安全地删除它们。

Chapter 3 Introduction to Cocos2D Animations and Actions70

1. Open Xcode, and under the Groups and Files section, select the Classes folder.

2. With the Classes folder selected, you can either press Option-z-N or right-click on the Classes folder and select New Group from the contextual menu. Create the following groups:n Constantsn Scenesn Layersn GameObjectsn EnemyObjectsn PowerUpsn Singletons

3. Drag the GameScene.h and GameScene.m files into the Scenes group.

4. Drag the BackgroundLayer and GameplayLayer .h and .m files into the Layers group.

5. Finally, drag the SpaceVikingAppDelegate .h and .m files into the Singletons group.

At this point your SpaceViking project layout should be similar to the one shown in Figure 3.5. The order of the groups is not important, only that they exist under Classesto keep your code organized. If you still have your HelloWorldScene .m and .h files inside your SpaceViking project, you can safely delete them.

Figure 3.5 Xcode Groups and File panel showing the groups created under the Classes folder in Space Viking

图 3.5 Xcode 组和文件面板展示了 Space Viking 项目在 classes 文件夹下创建的组

3.5.1 创建常量文件

在 Space Viking 中有些你将在不止一个类中使用的静态值。因此,你要创建一个

Constants.h 头文件,其中包含所有 Space Viking 中在多个类中使用的 #define 声明和常量。

现在,你已有一些组就位,用来组织 Space Viking 项目。是时候添加 Constants 和

CommonProtocol 类了。

1. 在 Xcode 中,选择 Constants 组(Classes > Constants)。2. 选择 File > New File(z-N 热键,或者在 Constants 文件夹上右击,从上下文菜单中

选择 New File),然后在 iOS 下选择 C and C++ 分类,并选中 Header File 选项。

3. 键入 Constants.h 作为文件名,点击 Finish。4. 选中 Constants.h 文件,键入如代码清单 3.3 所示代码。

代码清单 3.3 Constants.h 头文件

Organization, Constants, and Common Protocols 71

Creating the Constants FileSpace Viking has some static values that you will want to use in more than one class. For this purpose, you create a Constants.h header file, which contains all of the #define statements and other constants used by more than one class in Space Viking.

Now that you have the groups in place to keep Space Viking organized, it is time to add the Constants and CommonProtocol classes.

1. In Xcode, select the Constants group (Classes > Constants).

2. Select File > New File (z-N, or right-click on the Constants folder and select New File from the contextual menu), and then select C and C++ category under iOS, and choose the Header File option.

3. Enter Constants.h for the filename and click Finish.

4. Select the Constants.h file and enter the text shown in Listing 3.3.

Listing 3.3 Constants.h header file

// Constants.h

// Constants used in SpaceViking

#define kVikingSpriteZValue 100

#define kVikingSpriteTagValue 0

#define kVikingIdleTimer 3.0f

#define kVikingFistDamage 10

#define kVikingMalletDamage 40

#define kRadarDishTagValue 10

When you add a sprite to your layer, you can give it a z-order. In Space Viking, we give a unique z-order to each of the sprites to make it very clear which sprites are on the top and which are behind. The kVikingSpriteZValue is used to keep Ole the Viking in front of the other game characters. The number 100 is arbitrary, and it only has to be larger than the number of objects added to the same CCSpriteBatchNode.In other words, a z value of 0 is the backmost object, while positive numbers are in front, with the highest number being the object rendered in front of all of the others. If there were 500 enemy objects in Space Viking, you would want to make this number higher, such as 501 or 600.

Each CCNode object can hold an optional tag value: a unique integer value that other nodes can use to retrieve it. The enemy objects in Space Viking will use the kVikingSpriteTagValue to get the Viking object from the CCSpriteBatchNodeand query the Viking’s position and state. The default value of the tag is kCCNode-TagInvalid (-1), so 0 is a valid tag to use. Similarly, kRadarDishTagValue will be used in Chapter 5 to get a reference to the RadarDish class from the CCBatchNode.

Finally, there is a setting for the idle timer for the Viking, currently set to 3 seconds (as noted by 3.0f). If Ole is idle for 3 seconds, the Viking will perform a

Page 13: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  57 

Organization, Constants, and Common Protocols 71

Creating the Constants FileSpace Viking has some static values that you will want to use in more than one class. For this purpose, you create a Constants.h header file, which contains all of the #define statements and other constants used by more than one class in Space Viking.

Now that you have the groups in place to keep Space Viking organized, it is time to add the Constants and CommonProtocol classes.

1. In Xcode, select the Constants group (Classes > Constants).

2. Select File > New File (z-N, or right-click on the Constants folder and select New File from the contextual menu), and then select C and C++ category under iOS, and choose the Header File option.

3. Enter Constants.h for the filename and click Finish.

4. Select the Constants.h file and enter the text shown in Listing 3.3.

Listing 3.3 Constants.h header file

// Constants.h

// Constants used in SpaceViking

#define kVikingSpriteZValue 100

#define kVikingSpriteTagValue 0

#define kVikingIdleTimer 3.0f

#define kVikingFistDamage 10

#define kVikingMalletDamage 40

#define kRadarDishTagValue 10

When you add a sprite to your layer, you can give it a z-order. In Space Viking, we give a unique z-order to each of the sprites to make it very clear which sprites are on the top and which are behind. The kVikingSpriteZValue is used to keep Ole the Viking in front of the other game characters. The number 100 is arbitrary, and it only has to be larger than the number of objects added to the same CCSpriteBatchNode.In other words, a z value of 0 is the backmost object, while positive numbers are in front, with the highest number being the object rendered in front of all of the others. If there were 500 enemy objects in Space Viking, you would want to make this number higher, such as 501 or 600.

Each CCNode object can hold an optional tag value: a unique integer value that other nodes can use to retrieve it. The enemy objects in Space Viking will use the kVikingSpriteTagValue to get the Viking object from the CCSpriteBatchNodeand query the Viking’s position and state. The default value of the tag is kCCNode-TagInvalid (-1), so 0 is a valid tag to use. Similarly, kRadarDishTagValue will be used in Chapter 5 to get a reference to the RadarDish class from the CCBatchNode.

Finally, there is a setting for the idle timer for the Viking, currently set to 3 seconds (as noted by 3.0f). If Ole is idle for 3 seconds, the Viking will perform a

当添加一个精灵到层的时候,可以给精灵一个 z 轴值(z-order)。在 Space Viking 中,

我们赋给每个精灵一个唯一的 z 轴值,使哪一个精灵在上、哪一个精灵在下非常清晰。

kVikingSpriteZValue 用来使 Ole 在游戏中任何其他角色之前。数字 100 是随意的,它只需

要大于添加到同一 CCSpriteBatchNode 中的对象的数量即可。换而言之,一个为 0 的 z 值

代表最后面的一个对象,而正值在前面,有最大值的对象会在所有对象之前首先被渲染。

如果在 Space Viking 中有 500 个敌人对象,你需要使这个数值稍高点,例如 501 或 600。每一个 CCNode 对象可以容纳一个可选 tag 值 :其他节点可以用来检索的唯一整型值。

在 Space Viking 中的敌人对象将使用这个 kVikingSpriteTagValue,从 CCSpriteBatchNode中获取海盗对象的位置和状态。这个 tag 的默认值是 kCCNode-TagInvalid(–1),所以

0 是一个可以使用的有效标签值。类似地,将在第 5 章使用的 kRadarDishTagValue 会从

CCBatchNode 得到一个 RadarDish 类的引用。

最后,有一个用于 Viking 的空闲定时器的设置,当前设置为 3 秒(用 3.0f 标记)。如

果 Ole 空闲超过 3 秒,Viking 将执行一个呼吸动作吸引玩家继续进行。这些标签只是一个

可以赋给任何 CCNode 对象的唯一整型值,可用这个值检索从父类指向 CCNode 的引用。

3.5.2 通用协议文件

Objective-C 中的协议(Protocol)与其他语言(如 Java)中的接口(interface)非常相

似。简单地说,协议允许你指定一个类必须响应的方法,而不用知道方法是如何实现的。

可以把协议想象成一个合同,指定附着在其上的类会响应协议中指定的方法。

在 Space Viking 中, 当 机 器 人 攻 击 Ole 时,EnemyRobot 类 会 询 问 GameplayLayer层,创建一个 phaserBullet 对象。EnemyRobot 只是导入 GameplayLayer 是没有任何意义

的,因为所有其他类 GameplayLayer 本身导入了。通过创建一个 GameplayLayer 层委托

协议,EnemyRobot 类可以只访问 GameplayLayer 层中它需要的方法。对于 EnemyRobot,GameplayLayer 只有三个方法展示在 GameplayLayerDelegate 协议中,如代码清单 3.4 所示。

创建 CommonProtocols.h 文件的步骤如下。

1. 在 Xcode 中,选择 Constants 组。

2. 选择 File > New File(z-N 热键,或者在 Constants 文件夹上右击从上下文菜单中选

择 New File),然后在 iOS 下选择 C and C++ 分类,选中 Header File 选项。

3. 键入 CommonProtocols.h 作为文件名,点击 Finish。4. 选中 CommonProtocols.h 文件,键入如代码清单 3.4 所示代码。

Page 14: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 58  第一部分 Cocos2D 初接触

代码清单 3.4 CommonProtocols.h 头文件

Chapter 3 Introduction to Cocos2D Animations and Actions72

breathing action to entice the player to keep going. The tags are just unique integer values you can assign to any CCNode and use the values to retrieve a reference to the CCNode from their parent class.

Common Protocols FileProtocols in Objective-C are similar to interfaces in other languages such as Java. To put it simply, protocols allow you to specify the methods that a class will respond to, without knowing how those methods are implemented. Think of protocols as a contract stating that the class adhering to the protocol will respond to the methods defined in that protocol.

In Space Viking the EnemyRobot class has to ask the GameplayLayer to create a phaserBullet when the robot is attacking Ole the Viking. It does not make sense for EnemyRobot to just import GameplayLayer because of all of the other classes that GameplayLayer itself imports. By creating a GameplayLayer delegate protocol, the EnemyRobot class can access just the method it needs in GameplayLayer. To EnemyRobot, GameplayLayer only has the three methods shown in the Gameplay-LayerDelegate protocol in Listing 3.4.

To create the CommonProtocols.h file:

1. In Xcode, select the Constants group.

2. Select File > New File (z-N, or right-click on the Constants folder and select New File from the contextual menu), and then select C and C++ category and the Header File option.

3. Enter CommonProtocols.h for the filename, and click Finish.

4. Select the CommonProtocols.h file and enter the text shown in Listing 3.4.

Listing 3.4 CommonProtocols.h header file

// CommonProtocols.h

// SpaceViking

typedef enum {

kDirectionLeft,

kDirectionRight

} PhaserDirection;

typedef enum {

kStateSpawning,

kStateIdle,

kStateCrouching,

kStateStandingUp,

kStateWalking,

kStateAttacking,

kStateJumping,

kStateBreathing,

Organization, Constants, and Common Protocols 73

kStateTakingDamage,

kStateDead,

kStateTraveling,

kStateRotating,

kStateDrilling,

kStateAfterJumping

} CharacterStates; // 1

typedef enum {

kObjectTypeNone,

kPowerUpTypeHealth,

kPowerUpTypeMallet,

kEnemyTypeRadarDish,

kEnemyTypeSpaceCargoShip,

kEnemyTypeAlienRobot,

kEnemyTypePhaser,

kVikingType,

kSkullType,

kRockType,

kMeteorType,

kFrozenVikingType,

kIceType,

kLongBlockType,

kCartType,

kSpikesType,

kDiggerType,

kGroundType

} GameObjectType;

@protocol GameplayLayerDelegate

-(void)createObjectOfType:(GameObjectType)objectType

withHealth:(int)initialHealth

atLocation:(CGPoint)spawnLocation

withZValue:(int)ZValue;

-(void)createPhaserWithDirection:(PhaserDirection)phaserDirection

andPosition:(CGPoint)spawnPosition;

@end

The typedef enum lines simply ask the compiler to create a special variable type that can have only the values defined for that particular type, as shown in List-ing 3.4. Instead of keeping a numeric value for each possible state for the characters in Space Viking and trying to remember that a value of 1 represents idle and 4 repre-sents walking, you can just reference meaningful identifiers such as kStateIdle or kStateWalking. It makes the code in Space Viking much more readable and easier to understand.

Page 15: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  59 

Organization, Constants, and Common Protocols 73

kStateTakingDamage,

kStateDead,

kStateTraveling,

kStateRotating,

kStateDrilling,

kStateAfterJumping

} CharacterStates; // 1

typedef enum {

kObjectTypeNone,

kPowerUpTypeHealth,

kPowerUpTypeMallet,

kEnemyTypeRadarDish,

kEnemyTypeSpaceCargoShip,

kEnemyTypeAlienRobot,

kEnemyTypePhaser,

kVikingType,

kSkullType,

kRockType,

kMeteorType,

kFrozenVikingType,

kIceType,

kLongBlockType,

kCartType,

kSpikesType,

kDiggerType,

kGroundType

} GameObjectType;

@protocol GameplayLayerDelegate

-(void)createObjectOfType:(GameObjectType)objectType

withHealth:(int)initialHealth

atLocation:(CGPoint)spawnLocation

withZValue:(int)ZValue;

-(void)createPhaserWithDirection:(PhaserDirection)phaserDirection

andPosition:(CGPoint)spawnPosition;

@end

The typedef enum lines simply ask the compiler to create a special variable type that can have only the values defined for that particular type, as shown in List-ing 3.4. Instead of keeping a numeric value for each possible state for the characters in Space Viking and trying to remember that a value of 1 represents idle and 4 repre-sents walking, you can just reference meaningful identifiers such as kStateIdle or kStateWalking. It makes the code in Space Viking much more readable and easier to understand.

typedef enum 代码行简单地要求编译器创建一个特别的变量类型,可以只为特别的类

型定义值,如代码清单 3.4 所示。这里没有为 Space Viking 中的角色的每种可能状态保存值,

并试图记住数值 1 代表空闲、数值 4 代表行走,你可以引用有意义的标识符,如 kStateIdle 或

kStateWalking。这样使 Space Viking 中的代码可读性非常强,同时更加易于理解。

GameplayLayerDelegate 协议定义了两个方法,Space Viking 中的角色会使用到。特

别是,SpaceCargoShip 和 EnemyRobot 使用这些方法,让 GameplayLayer 层创建道具和激

光枪子弹。SpaceCargoShip 不需要了解任何有关 GameplayLayer 更进一步的详情,只是

仅仅响应那两个方法。使用协议可以隐藏实现的复杂性,让游戏的代码更加模块化、更可

重用。如果在稍后你想用其他类替换 GameplayLayer,所有你需要做的是确保新的类遵循

GameplayLayer-Delegate 协议。

有了这两个特别的头文件,是时候创建 GameObject 类和 GameCharacter 类了。

3.6 GameObject 和 GameCharacter 类在 Space Viking 中,GameObject 类和 GameCharacter 类是游戏内元素的基础。Viking 类、

RadarDish 类和 EnemyRobot 类都从 GameObject 类和 GameCharacter 类继承功能。这些基

类可以作为一种模型在构建自己的游戏时使用。GameObject 类包含必要的功能,读取在本

章早些时候创建的属性列表文件,允许 Space Viking 中的所有对象基于这些文件设置它们

自己的动画。

3.6.1 创建 GameObject

GameObject 类是 Space Viking 中所有对象的起点。GameObject 类继承自 CCSprite 类,

拥有运行动作并知道如何渲染自己的纹理的能力。GameObject 类中加入了 Space Viking 游

戏中使用的一些特定方法和成员变量实例。

按如下步骤创建 GameObject 的头文件和实现文件:

1. 在 Xcode 中,选择 GameObjects 组,右击。

2. 从 上 下 文 菜 单 中 选 择 New File, 在 iOS 下 选 中 Cocoa Touch 种 类, 并 选 择

Objective-C 作为文件类型,点击 Next。3. 对于子类的字段,输入 CCSprite,点击 Next。4. 输入 GameObject 作为文件名,点击 Save。打开 GameObject.h 头文件,改变文件内容以使之如代码清单 3.5 所示。

Page 16: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 60  第一部分 Cocos2D 初接触

代码清单 3.5 GameObject.h 头文件

Chapter 3 Introduction to Cocos2D Animations and Actions74

The GameplayLayerDelegate protocol defines two methods that the characters in Space Viking will use. In particular, the SpaceCargoShip and the EnemyRobotuse these methods to have the GameplayLayer create the power-ups and the phaser bullets. The SpaceCargoShip does not have to know anything further about the GameplayLayer, only that it responds to these two methods. Using protocols can hide the complexity of the implementation and allow your game code to be more modular and reusable. If you want to later replace the GameplayLayer with another class, all you need to do is ensure that the new class adheres to the GameplayLayer-Delegate protocol.

With these two header files out of the way, it is time to create the GameObjectand GameCharacter classes.

The GameObject and GameCharacter ClassesThe GameObject and GameCharacter classes are the foundations of the in-game elements in Space Viking. The Viking, RadarDish, and EnemyRobot classes all inherit functionality from GameObject and GameCharacter. These base classes can serve as a model that you can use when building your own games. The GameObjectclass contains the functionality need to read the property list files you created earlier in this chapter, allowing all of the Space Viking objects to set up their animations based on these files.

Creating the GameObjectThe GameObject class is the starting point for all of the objects in Space Viking. The GameObject class inherits from the CCSprite class, allowing it the ability to run actions and know how to render its texture. The GameObject class adds some specific methods and instance variables used by your Space Viking game.

Follow these steps to create the GameObject header and implementation files:

1. In Xcode, select the GameObjects group and right-click.

2. From the contextual menu, select New File, and choose the Cocoa Touch cat-egory under iOS and Objective-C class as the file type, and click Next.

3. For the Subclass field, enter CCSprite and click Next.

4. Enter GameObject for the filename and click Save.

Open the GameObject.h header file and change the contents to match the code in Listing 3.5.

Listing 3.5 GameObject.h header file

// GameObject.h

// SpaceViking

//

The GameObject and GameCharacter Classes 75

#import <Foundation/Foundation.h>

#import "cocos2d.h"

#import "Constants.h"

#import "CommonProtocols.h"

@interface GameObject : CCSprite {

BOOL isActive;

BOOL reactsToScreenBoundaries;

CGSize screenSize;

GameObjectType gameObjectType;

}

@property (readwrite) BOOL isActive;

@property (readwrite) BOOL reactsToScreenBoundaries;

@property (readwrite) CGSize screenSize;

@property (readwrite) GameObjectType gameObjectType;

-(void)changeState:(CharacterStates)newState;

-(void)updateStateWithDeltaTime:(ccTime)deltaTimeandListOfGameObjects:(CCArray*)listOfGameObjects;

-(CGRect)adjustedBoundingBox;

-(CCAnimation*)loadPlistForAnimationWithName:(NSString*)animationNameandClassName:(NSString*)className;

@end

Take a close look at the @interface declaration line. The GameObject : CCSprite code is what sets up the GameObject class as a subclass of CCSprite. All of the methods available to the CCSprite are now part of GameObject. You can see defined in Listing 3.5 some of the instance variables you will use in Space Viking that are not part of the base CCSprite class. In addition to the instance variables, there are the changeState, updateStateWithDeltaTime, adjustedBoundingBox, and loadPlistForAnimationWithName methods. To see what these methods do, open the GameObject.m implementation file and replace the content with the code shown in Listings 3.6 and 3.7.

Listing 3.6 GameObject.m implementation file (part 1 of 2)

// GameObject.m

// SpaceViking

//

#import "GameObject.h"

@implementation GameObject

@synthesize reactsToScreenBoundaries;

@synthesize screenSize;

@synthesize isActive;

@synthesize gameObjectType;

仔细看看 @interface 声明这一行。GameObject : CCSprite 这行代码设置 GameObject 类是 CCSprite 的一个子类。CCSprite 类所有可用的方法现在是 GameObject 的一部分。你可

以看到代码清单 3.5 中定义了将在 Space Viking 中使用的变量实例,这些变量实例并不是基

类 CCSprite 的一部分。除了变量实例以外,还有 changeState、updateStateWithDeltaTime、adjustedBoundingBox 和 loadPlistForAnimationWithName 方法。要看这些方法的实现,打开

实现文件 GameObject.m,改变文件内容以如代码清单 3.6 和代码清单 3.7 所示。

代码清单 3.6 GameObject.m 实现文件(1/2)

The GameObject and GameCharacter Classes 75

#import <Foundation/Foundation.h>

#import "cocos2d.h"

#import "Constants.h"

#import "CommonProtocols.h"

@interface GameObject : CCSprite {

BOOL isActive;

BOOL reactsToScreenBoundaries;

CGSize screenSize;

GameObjectType gameObjectType;

}

@property (readwrite) BOOL isActive;

@property (readwrite) BOOL reactsToScreenBoundaries;

@property (readwrite) CGSize screenSize;

@property (readwrite) GameObjectType gameObjectType;

-(void)changeState:(CharacterStates)newState;

-(void)updateStateWithDeltaTime:(ccTime)deltaTimeandListOfGameObjects:(CCArray*)listOfGameObjects;

-(CGRect)adjustedBoundingBox;

-(CCAnimation*)loadPlistForAnimationWithName:(NSString*)animationNameandClassName:(NSString*)className;

@end

Take a close look at the @interface declaration line. The GameObject : CCSprite code is what sets up the GameObject class as a subclass of CCSprite. All of the methods available to the CCSprite are now part of GameObject. You can see defined in Listing 3.5 some of the instance variables you will use in Space Viking that are not part of the base CCSprite class. In addition to the instance variables, there are the changeState, updateStateWithDeltaTime, adjustedBoundingBox, and loadPlistForAnimationWithName methods. To see what these methods do, open the GameObject.m implementation file and replace the content with the code shown in Listings 3.6 and 3.7.

Listing 3.6 GameObject.m implementation file (part 1 of 2)

// GameObject.m

// SpaceViking

//

#import "GameObject.h"

@implementation GameObject

@synthesize reactsToScreenBoundaries;

@synthesize screenSize;

@synthesize isActive;

@synthesize gameObjectType;

Chapter 3 Introduction to Cocos2D Animations and Actions76

-(id) init {

if((self=[super init])){

CCLOG(@"GameObject init");

screenSize = [CCDirector sharedDirector].winSize;

isActive = TRUE;

gameObjectType = kObjectTypeNone;

}

return self;

}

-(void)changeState:(CharacterStates)newState {

CCLOG(@"GameObject->changeState method should be overridden");

}

-(void)updateStateWithDeltaTime:(ccTime)deltaTimeandListOfGameObjects:(CCArray*)listOfGameObjects {

CCLOG(@"updateStateWithDeltaTime method should be overridden");

}

-(CGRect)adjustedBoundingBox {

CCLOG(@"GameObect adjustedBoundingBox should be overridden");

return [self boundingBox];

}

The first part of GameObject.m in Listing 3.6 has the init method that sets up the default values for the instance variables and calls the superclass initializer; in this case, CCSprite. Looking at these three methods, you can see that updateStateWith-DeltaTime and adjustedBoundingBox are stubs and do nothing more than call CCLOG to warn that they should be overridden. This is because you will not be using the GameObject class directly; instead, you will use it as the base for the other object classes in Space Viking. This means that the Viking, SpaceCargoShip, and other classes should have methods with the same name, essentially overriding these three default methods.

The updateStateWithDeltaTime method is what the GameplayLayer class calls to update game objects on every frame. This method also contains the AI for the objects and is where the objects perform the actions that define their individual behavior.

The changeState method is used by the objects to transition from one state to another. The state change often triggers custom animations that represent each state.

Finally, the adjustedBoundingBox method does exactly what the name implies. In your game objects, this method adjusts the default sprite’s bounding box to com-pensate for the transparent space. Figure 3.6 shows the transparent space around the Viking sprite with a padding of 26 pixels behind and 66 pixels in front of the Viking. If you were to just take the default CCSprite boundingBox, it would

Page 17: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  61 

Chapter 3 Introduction to Cocos2D Animations and Actions76

-(id) init {

if((self=[super init])){

CCLOG(@"GameObject init");

screenSize = [CCDirector sharedDirector].winSize;

isActive = TRUE;

gameObjectType = kObjectTypeNone;

}

return self;

}

-(void)changeState:(CharacterStates)newState {

CCLOG(@"GameObject->changeState method should be overridden");

}

-(void)updateStateWithDeltaTime:(ccTime)deltaTimeandListOfGameObjects:(CCArray*)listOfGameObjects {

CCLOG(@"updateStateWithDeltaTime method should be overridden");

}

-(CGRect)adjustedBoundingBox {

CCLOG(@"GameObect adjustedBoundingBox should be overridden");

return [self boundingBox];

}

The first part of GameObject.m in Listing 3.6 has the init method that sets up the default values for the instance variables and calls the superclass initializer; in this case, CCSprite. Looking at these three methods, you can see that updateStateWith-DeltaTime and adjustedBoundingBox are stubs and do nothing more than call CCLOG to warn that they should be overridden. This is because you will not be using the GameObject class directly; instead, you will use it as the base for the other object classes in Space Viking. This means that the Viking, SpaceCargoShip, and other classes should have methods with the same name, essentially overriding these three default methods.

The updateStateWithDeltaTime method is what the GameplayLayer class calls to update game objects on every frame. This method also contains the AI for the objects and is where the objects perform the actions that define their individual behavior.

The changeState method is used by the objects to transition from one state to another. The state change often triggers custom animations that represent each state.

Finally, the adjustedBoundingBox method does exactly what the name implies. In your game objects, this method adjusts the default sprite’s bounding box to com-pensate for the transparent space. Figure 3.6 shows the transparent space around the Viking sprite with a padding of 26 pixels behind and 66 pixels in front of the Viking. If you were to just take the default CCSprite boundingBox, it would

在代码清单 3.6 中 GameObject.m 实现文件的第一部分,init 方法设置了实例变量的默

认值,并调用父类的初始化器 ;在这里父类是 CCSprite 类。纵观这三个方法,你可以看到

updateStateWithDeltaTime 和 adjustedBoundingBox 只是存根,只调用 CCLOG 发出警告而没有

做其他更多的事情,它们应该被覆写。这是因为你不能直接使用 GameObject 类 ;取而代之,

在 Space Viking 中你将使用它作为其他对象类的基类。这意味着 Viking、SpaceCargoShip 和

其他类有一些方法有同样的名字,从本质上覆写了这三个默认的方法。

updateStateWithDeltaTime 方法由 GameplayLayer 调用,用来在每一帧更新游戏对象。

这个方法也包含对象的人工智能,并且是对象完成定义它们行为的动作的地方。

当对象从一种状态转换到另一种状态时,使用 changeState 方法。通常对象状态变化会

触发表示每个状态的自定义动画。

最后,adjustedBoundingBox 方法完成它名字隐含的事务。在你的游戏对象中,这个方

法调整默认的精灵的包围盒,以

弥补透明空间。图 3.6 显示了一个

围绕着 Viking 精灵的透明空间,

Viking 精灵后面有 26 像素的透明

空间(称为补白,padding),前部

有 66 像素的透明空间。如果只采

用 默 认 的 CCSprite boundingBox,精灵将只采用这些(后 26,前 66)透明像素,碰撞检测不是很准确。每次被触及时,在有

实际意义 / 非透明部分之前,会先触碰到你游戏中的对象。

The GameObject and GameCharacter Classes 77

include these transparent pixels, and your collision detection would be inaccurate. Objects in your game would come into contact before the meaningful/nontranparent part of the graphics ever touched.

Figure 3.6 Transparent space around the Viking sprite/image

A Word on Bounding BoxesThe bounding boxes in Cocos2D are actually what are referred to as axis-aligned bound-ing boxes, or AABB for short. This is simply a box around your sprite texture (your image), with the axis of the box lined up with the axis of the coordinate system being used. The bounding box is really useful in determining when two or more objects are overlapping or colliding, without having to resort to a physics engine.

In Listing 3.7 you can see the loadPlistForAnimationWithName method, which is responsible for setting up the animations based on the data stored in the plist files. This method is declared and implemented in GameObject, but it is actually called and used by the individual classes such as the RadarDish and Viking. This method is passed the animation name as well as the class name. The class name is used to find the plist file, since the files are named after the classes they belong to, such as RadarDish.plist for the RadarDish class. Each plist file dictionary may contain several mini-dictionaries for each animation, and the animation name is used to extract the specific dictionary for the animation name passed in. Add the contents of Listing 3.7 below the code you just entered in GameObject.m.

Listing 3.7 GameObject.m implementation file (part 2 of 2)

-(CCAnimation*)loadPlistForAnimationWithName:(NSString*)animationNameandClassName:(NSString*)className {

CCAnimation *animationToReturn = nil;

NSString *fullFileName =

[NSString stringWithFormat:@"%@.plist",className];

NSString *plistPath;

// 1: Get the Path to the plist file

NSString *rootPath =

[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

图 3.6 环绕维京海盗精灵 / 图片的透明空间

Page 18: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 62  第一部分 Cocos2D 初接触

关于包围盒(Bounding Boxes)在 Cocos2D 中的包围盒实际上是被称为轴 – 对齐(axis-aligned)的包围盒,后者缩写

为 AABB。这个简单的盒子围绕着精灵纹理(你的图像),盒子的轴与正在使用的坐标系统

的轴平行。包围盒真正的用处在于,确定两个或多个对象是否重叠或碰撞,而不必求助物

理引擎。

在代码清单 3.7 中,可以看到 loadPlistForAnimationWithName 方法,负责基于属性

列表文件中存储的数据设置动画。这个方法在 GameObject 中声明和实现,但是只被如

RadarDish 和 Viking 这样的独立类真正地调用和使用。这个方法可通过动画名和类名来传

递。类名用来寻找属性列表文件,因为文件一般用它所属的类名来命名,如 RadarDish 类的

文件 RadarDish. plist。每个属性列表文件字典可能包含许多对应动画的迷你字典,动画的名

字用来提取特别的动画字典。在 GameObject.m 文件中添加的代码之后,添加代码清单 3.7中的内容。

代码清单 3.7 GameObject.m 实现文件(2/2)

The GameObject and GameCharacter Classes 77

include these transparent pixels, and your collision detection would be inaccurate. Objects in your game would come into contact before the meaningful/nontranparent part of the graphics ever touched.

Figure 3.6 Transparent space around the Viking sprite/image

A Word on Bounding BoxesThe bounding boxes in Cocos2D are actually what are referred to as axis-aligned bound-ing boxes, or AABB for short. This is simply a box around your sprite texture (your image), with the axis of the box lined up with the axis of the coordinate system being used. The bounding box is really useful in determining when two or more objects are overlapping or colliding, without having to resort to a physics engine.

In Listing 3.7 you can see the loadPlistForAnimationWithName method, which is responsible for setting up the animations based on the data stored in the plist files. This method is declared and implemented in GameObject, but it is actually called and used by the individual classes such as the RadarDish and Viking. This method is passed the animation name as well as the class name. The class name is used to find the plist file, since the files are named after the classes they belong to, such as RadarDish.plist for the RadarDish class. Each plist file dictionary may contain several mini-dictionaries for each animation, and the animation name is used to extract the specific dictionary for the animation name passed in. Add the contents of Listing 3.7 below the code you just entered in GameObject.m.

Listing 3.7 GameObject.m implementation file (part 2 of 2)

-(CCAnimation*)loadPlistForAnimationWithName:(NSString*)animationNameandClassName:(NSString*)className {

CCAnimation *animationToReturn = nil;

NSString *fullFileName =

[NSString stringWithFormat:@"%@.plist",className];

NSString *plistPath;

// 1: Get the Path to the plist file

NSString *rootPath =

[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

Chapter 3 Introduction to Cocos2D Animations and Actions78

NSUserDomainMask, YES) objectAtIndex:0];

plistPath = [rootPath stringByAppendingPathComponent:fullFileName];

if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {

plistPath = [[NSBundle mainBundle]

pathForResource:className ofType:@"plist"];

}

// 2: Read in the plist file

NSDictionary *plistDictionary =

[NSDictionary dictionaryWithContentsOfFile:plistPath];

// 3: If the plistDictionary was null, the file was not found.

if (plistDictionary == nil) {

CCLOG(@"Error reading plist: %@.plist", className);

return nil; // No Plist Dictionary or file found

}

// 4: Get just the mini-dictionary for this animation

NSDictionary *animationSettings =

[plistDictionary objectForKey:animationName];

if (animationSettings == nil) {

CCLOG(@"Could not locate AnimationWithName:%@",animationName);

return nil;

}

// 5: Get the delay value for the animation

float animationDelay =

[[animationSettings objectForKey:@"delay"] floatValue];

animationToReturn = [CCAnimation animation];

[animationToReturn setDelay:animationDelay];

// 6: Add the frames to the animation

NSString *animationFramePrefix =

[animationSettings objectForKey:@"filenamePrefix"];

NSString *animationFrames =

[animationSettings objectForKey:@"animationFrames"];

NSArray *animationFrameNumbers =

[animationFrames componentsSeparatedByString:@","];

for (NSString *frameNumber in animationFrameNumbers) {

NSString *frameName =

[NSString stringWithFormat:@"%@%@.png",

animationFramePrefix,frameNumber];

[animationToReturn addFrame:

[[CCSpriteFrameCache sharedSpriteFrameCache]

spriteFrameByName:frameName]];

}

Page 19: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  63 

Chapter 3 Introduction to Cocos2D Animations and Actions78

NSUserDomainMask, YES) objectAtIndex:0];

plistPath = [rootPath stringByAppendingPathComponent:fullFileName];

if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {

plistPath = [[NSBundle mainBundle]

pathForResource:className ofType:@"plist"];

}

// 2: Read in the plist file

NSDictionary *plistDictionary =

[NSDictionary dictionaryWithContentsOfFile:plistPath];

// 3: If the plistDictionary was null, the file was not found.

if (plistDictionary == nil) {

CCLOG(@"Error reading plist: %@.plist", className);

return nil; // No Plist Dictionary or file found

}

// 4: Get just the mini-dictionary for this animation

NSDictionary *animationSettings =

[plistDictionary objectForKey:animationName];

if (animationSettings == nil) {

CCLOG(@"Could not locate AnimationWithName:%@",animationName);

return nil;

}

// 5: Get the delay value for the animation

float animationDelay =

[[animationSettings objectForKey:@"delay"] floatValue];

animationToReturn = [CCAnimation animation];

[animationToReturn setDelay:animationDelay];

// 6: Add the frames to the animation

NSString *animationFramePrefix =

[animationSettings objectForKey:@"filenamePrefix"];

NSString *animationFrames =

[animationSettings objectForKey:@"animationFrames"];

NSArray *animationFrameNumbers =

[animationFrames componentsSeparatedByString:@","];

for (NSString *frameNumber in animationFrameNumbers) {

NSString *frameName =

[NSString stringWithFormat:@"%@%@.png",

animationFramePrefix,frameNumber];

[animationToReturn addFrame:

[[CCSpriteFrameCache sharedSpriteFrameCache]

spriteFrameByName:frameName]];

}

The GameObject and GameCharacter Classes 79

return animationToReturn;

}

@end

Now let’s examine the sections in Listing 3.7, as noted by the numbered comment lines in the code:

n Sections 1 and 2 are from the standard Apple templates on reading property list files:n Section 1 tries to get the actual file system path to the property list file. This

is where the className that was passed into the method is used to try to determine the path from the application bundle.

n Section 2 uses built-in methods to read and parse the property list file, returning a dictionary with the entire contents of the plist file. This is an NSDictionary containing all of the animation mini-dictionaries in this single file.

n Section 3 is just a quick check to make sure the NSDictionary was not empty.n Section 4 is where the mini-dictionary for the particular animation passed in

as animationName is extracted. The mini-dictionary (named animation-Settings in the code) contains the filename prefix, delay, and list of animation frames. After extracting the animationSettings dictionary, a quick check is made to ensure it is not empty.

n In Section 5, the delay value is retrieved from animationSettings, and the CCAnimation object is created with the delay. Since the delay is often frac-tions of a second, this is stored as a f loat value. You can have the delay in the CCAnimate action as you learned, or as part of the CCAnimation object itself.

n Section 6 is where all of the animation frames are loaded into the animation. The first step is to get the prefix for the filename for each of the frames. (For example, the RadarDish animations use the radar_ prefix.) The next step is to break apart the string of frame numbers into an NSArray of just the number themselves. A convenient NSString method called componentsSeparated-ByString allows you to split up the string into comma-separated values in one line of code. The for loop iterates through the NSArray of frame numbers and adds the CCSpriteFrame to the animation. Each frame number becomes a call to add a CCSpriteFrame where the name consists of the prefix, the frame num-ber, and .png. Finally, the newly created and loaded animation is returned.

The key point to understand about the code in Listing 3.7 is that by using the loadPlistForAnimationWithName method, you can load the animation settings for all of the game objects in Space Viking. If you look closely at section 7, you will see the key names filenamePrefix and animationFrames, which you set up in the plist files at the beginning of this chapter. Make sure your GameObject.m implementation file contains the code from both Listings 3.6 and 3.7.

现在让我们开始解释代码清单 3.7 中的代码片段,注意代码中带编号的注释行。

• 片段 1、2 是来自标准的苹果读取属性列表文件的模板代码:

• 片段 1,试图获取属性列表文件的真正的文件系统路径。在这里 className 变量被传

递到方法中,用来确定应用程序包的路径。

• 片段 2,用内置方法读取和解析属性列表文件,返回一个包含属性列表文件中全部内

容的数据字典。返回对象是一个 NSDictionary,其中包含文件中所有动画的迷你字典。

• 片段 3,是一个快速检查,判断 NSDictionary 对象是否为非空。

• 片段 4,在这里是指定动画的迷你字典,通过变量 animationName 被提取出来。迷你

字典(在代码中命名为 animation-Settings)包含文件名前缀、延迟时间和动画帧列

表。在提取 animationSettings 字典后,需要一个快速检查确定字典不为空。

• 在片段 5 中,delay 值从 animationSettings 检索得到,CCAnimation 对象创建时带一

个延迟量。由于延迟常常是一秒的片段(时间非常短),所以作为浮点数存储。你可

以像之前学习的那样处置 CCAnimate 动作中的延迟,或者使其成为 CCAnimation 对

象的一部分。

Page 20: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 64  第一部分 Cocos2D 初接触

• 片段 6,所有动画帧被加载到动画中。第一步是获取每一帧的文件名前缀。(例如,

RadarDish 动画使用 radar_ 前缀。)下一步,把帧名字中的字符串序号分离出来,仅以

数字形式加入到 NSArray 数组。 方便使用 NSString 方法调用 componentsSeparated-ByString,允许在一行代码中用逗号来分隔字符串。for 循环遍历 NSArray 中的帧序号,

添加 CCSpriteFrame 帧对象到动画。每一个帧序号变成一个调用,以添加由前缀、序

号和 .png 命名的 CCSpriteFrame 对象。最终,新创建并加载的动画返回。6

了解代码清单 3.7 中代码含义的关键点是,使用 loadPlistForAnimationWithName 方法,

你可以在 Space Viking 中加载所有游戏对象的动画数据设置。如果你仔细看了片段 7,可以

看到关键名 filenamePrefix 和 animationFrames,这是本章开始时设置在属性列表文件中的。

确认你的 GameObject.m 实现文件包含代码清单 3.6 ~代码清单 3.7 中的全部代码。

本章每节的最后是你构建项目的很好的机会,能确保你没有引入任何错误输入和程序

错误。可使用 -B 或从 Product 菜单中选择 Build 来构建项目。

3.6.2 创建 GameCharacter 类

在 Space Viking 中,道具和 SpaceCargoShip 对象直接继承自 GameObject,所以它们

的行为非常简单。在 Space Viking 中继承自 GameCharacter 类的其他对象,为这些对象提

供了状态机智能(state machine brain)。GameCharacter 类提供实例变量以保存如对象生

命值类的数值。该类也提供了一个便利方法,以确保所有游戏元素都在屏幕边界内。创建

GameCharacter 头文件和实现文件的步骤如下。

1. 在 Xcode 中,在 GameObjects 组上右击。

2. 选中 New File,在 iOS 下选中 Cocoa Touch category,并且选择 Objective-C 作为文

件类型,点击 Next。3. 为 Subclass 字段输入 GameObject,点击 Next。4. 输入 GameCharacter 作为文件名,点击 Finish。打开 GameCharacter.h 头文件,修改其中的内容以如代码清单 3.8 所示。

代码清单 3.8 GameCharacter.h 头文件

Chapter 3 Introduction to Cocos2D Animations and Actions80

At the end of each section in this chapter, it is good practice for you to build the project and ensure you have not introduced any typos or errors. Use c-B or select Build from the Product menu to build the project.

Creating the GameCharacter ClassIn Space Viking, power-ups and the SpaceCargoShip inherit directly from GameObject, as their behavior is very simple. The other objects in Space Vikinginherit from the GameCharacter class, providing these objects with a state machine brain. The GameCharacter class provides instance variables for holding the current state as well as the character’s health. The class also provides a handy method to ensure that all of the game elements remain within the boundaries of the screen. To create the GameCharacter header and implementation files:

1. In Xcode, right-click on the GameObjects group.

2. Select New File, and choose the Cocoa Touch category under iOS and Objective-C class as the file type, and click Next.

3. For the Subclass field, enter GameObject and click Next.

4. Enter GameCharacter for the filename and click Finish.

Open the GameCharacter.h header file and change the contents to match the code in Listing 3.8.

Listing 3.8 GameCharacter.h header file

// GameCharacter.h

// SpaceViking

#import <Foundation/Foundation.h>

#import "GameObject.h"

@interface GameCharacter : GameObject {

int characterHealth;

CharacterStates characterState;

}

-(void)checkAndClampSpritePosition;

-(int)getWeaponDamage;

@property (readwrite) int characterHealth;

@property (readwrite) CharacterStates characterState;

@end

In Listing 3.8, you can see that two new instance variables and methods are added to all objects inheriting from GameCharacter. Open the GameCharacter.m imple-mentation file and replace the template code with the contents of Listing 3.9.

 把字符串中的数字字符变量转换成数字变量。—译者注

Page 21: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

第 3 章 Cocos2D 动画和动作概述  65 

Chapter 3 Introduction to Cocos2D Animations and Actions80

At the end of each section in this chapter, it is good practice for you to build the project and ensure you have not introduced any typos or errors. Use c-B or select Build from the Product menu to build the project.

Creating the GameCharacter ClassIn Space Viking, power-ups and the SpaceCargoShip inherit directly from GameObject, as their behavior is very simple. The other objects in Space Vikinginherit from the GameCharacter class, providing these objects with a state machine brain. The GameCharacter class provides instance variables for holding the current state as well as the character’s health. The class also provides a handy method to ensure that all of the game elements remain within the boundaries of the screen. To create the GameCharacter header and implementation files:

1. In Xcode, right-click on the GameObjects group.

2. Select New File, and choose the Cocoa Touch category under iOS and Objective-C class as the file type, and click Next.

3. For the Subclass field, enter GameObject and click Next.

4. Enter GameCharacter for the filename and click Finish.

Open the GameCharacter.h header file and change the contents to match the code in Listing 3.8.

Listing 3.8 GameCharacter.h header file

// GameCharacter.h

// SpaceViking

#import <Foundation/Foundation.h>

#import "GameObject.h"

@interface GameCharacter : GameObject {

int characterHealth;

CharacterStates characterState;

}

-(void)checkAndClampSpritePosition;

-(int)getWeaponDamage;

@property (readwrite) int characterHealth;

@property (readwrite) CharacterStates characterState;

@end

In Listing 3.8, you can see that two new instance variables and methods are added to all objects inheriting from GameCharacter. Open the GameCharacter.m imple-mentation file and replace the template code with the contents of Listing 3.9.

在代码清单 3.8 中,你可以看到两个新的变量实例和方法被添加到继承自 GameCharacter的所有对象中。打开 GameCharacter.m 实现文件,用代码清单 3.9 中的内容替换模板代码。

代码清单 3.9 GameCharacter.m 实现文件

The GameObject and GameCharacter Classes 81

Listing 3.9 GameCharacter.m implementation file

// GameCharacter.m

// SpaceViking

#import "GameCharacter.h"

@implementation GameCharacter

@synthesize characterHealth;

@synthesize characterState;

-(void) dealloc {

[super dealloc];

}

-(int)getWeaponDamage {

// Default to zero damage

CCLOG(@"getWeaponDamage should be overridden");

return 0;

}

-(void)checkAndClampSpritePosition {

CGPoint currentSpritePosition = [self position];

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {

// Clamp for the iPad

if (currentSpritePosition.x < 30.0f) {

[self setPosition:ccp(30.0f, currentSpritePosition.y)];

} else if (currentSpritePosition.x > 1000.0f) {

[self setPosition:ccp(1000.0f, currentSpritePosition.y)];

}

} else {

// Clamp for iPhone, iPhone 4, or iPod touch

if (currentSpritePosition.x < 24.0f) {

[self setPosition:ccp(24.0f, currentSpritePosition.y)];

} else if (currentSpritePosition.x > 456.0f) {

[self setPosition:ccp(456.0f, currentSpritePosition.y)];

}

}

}

@end

The getWeaponDamage method provides a quick method to determine how much damage to inf lict when an enemy or the Viking attacks. In the Viking this value will change if Ole is using his fists or the Mallet.

The checkAndClampSpritePosition is used to ensure that an object stays in the confines of the screen. It checks the current object’s position and repositions

Page 22: Cocos2D 动画和动作概述images.china-pub.com/ebook3765001-3770000/3768097/ch03.pdf58 Chapter 3 Introduction to Cocos2D Animations and Actions To recap, you first create a CCAnimation

 66  第一部分 Cocos2D 初接触

getWeaponDamage 方法提供一个快速方法,以判断敌人或 Viking(海盗 Ole)攻击的

伤害程度有多大。对 Viking 而言,攻击伤害值随着 Ole 使用拳头或锤子而发生改变。

checkAndClampSpritePosition 方法用来确保一个对象停留在屏幕范围内。这个方法检

查当前对象的坐标,如果对象超出屏幕的左右范围太大则重新定位这个对象。这有助于保

证 Ole 和敌人时时刻刻都在屏幕上。通过把这个方法加入到 GameCharacter 类中,可以避

免在你的每一个游戏角色类中都输入冗余代码。

点与像素

记 住 Cocos2D 使 用 点 系 统 在 iPhone 普 通 屏 幕(240×320 像 素 ) 和 视 网 膜 屏 幕

(480×960 像素)上定位对象。在非视网膜屏幕上一个点相当于一个像素,但在视网膜屏幕

上一个点相当于两个像素。当你设置 Ole 的位置为 24 像素时,在 iPhone 3G 和 iPhone 3GS上是相当于从左边起真正的 24 个像素。在使用视网膜屏幕的 iPhone 4 上,相当于从左边

起 48 个像素。在两种情况下,对于游戏者来说,Ole 看起来在屏幕上相同的位置。你可以

在以下网址阅读更多关于 Cocos2D 点系统的信息 :www.cocos2d-iphone.org/wiki/doku.php/prog_guide:how_to_develop_retinadisplay_games_in_cocos2d。

再一次,点击 -B 或从 Product 菜单中选中 Build,确认你的代码没有输入错误和编译

错误。完成 GameObject 类和 GameCharacter 类创建后,在下一章你将更深入一些,海盗

Ole 会遭遇第一个敌人。

3.7 小结

在本章中学习了 Cocos2D 的动画、CCAnimationCache 类和 Space Viking 中的游戏设计。

尽管你无法看到切实的成果,但打好基础对于为游戏添加更多敌人和其他角色是很必要的。

在完成本章内容后请翻到第 4 章,学习如何为 Space Viking 添加来势汹汹的敌人。

3.8 习题

1. 思考下你的游戏:你将如何设计类和对象的继承结构?

2. 如 何 在 GameObject 对 象 内 部 修 改 loadPlistForAnimationWithName 方 法, 以 使

CCAnimations 对象添加到 CCAnimationCache 中?

提示 如果你被难住了,可查看本章源代码中被注释的解决方案。