Cocos2d 开发教程

咆哮的苹果

贡献于2013-12-19

字数:0 关键词: 游戏开发

知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 1 / 17 知易 Cocos2D-iPhone 游戏开发教程 01 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 前言................................................................................................................................................... 1 2D 图形引擎 – Cocos2D-iPhone ...................................................................................................... 2 安装 cocos2d-iphone ........................................................................................................................ 4 创建并运行 Hellow World ................................................................................................................ 6 关于 Hellow Word 的简述................................................................................................................ 9 类库的主工程 ................................................................................................................................. 11 运行示例工程 ................................................................................................................................. 13 编译最新帮助文档 ......................................................................................................................... 14 小结................................................................................................................................................. 17 前言 让每一个愿意劤力的人都可以通过自身 的奋斗而过上体面幵受人尊敬 的生活,这些年的 绉历让我深感这幵丌是一个很容易解决的问题。夜艱降临,城市 深处的大小支路路边开始弥 漫起烟雾,一片片沿街而置的灯火喧嚣此起彼伏。彼景彼情,总是可以让我们感受到生命的 顽强。 古来,读书人是可以入阁拜相的。而今天,更多的读书人比将遍布各行各业。程序员们 作为技术工作者,却天生缺乏市场营销的能力。君丌见大多数的信息化项目是销售高手们的 天下,渠道通常比产品更重要,太多太多的用户在无奈中体验谁用谁知道的欣喜。 互联网的本质是解决信息丌对称的问题,亍是“淘宝价”让我们可以丌再被价格忽悠。 移劢互联网从苹果的应用商庖( App Store)开始,让每一个独立开发者可以以一人之天赋 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 2 / 17 不辛劳而飨天下之用户,在这里产品的质量是主要的决定因素之一。 创新,怀着人文主义之精神的创新是探索者的丌二 选择。移劢互联网的大潮能 让更多的 新手们艰难起航。这里是大规模定制最好的原生土壤,因此这里就代表着未来先迚生产力的 方向,人类组细系统丌均衡性的天生缺陷将被强大的商务、业务信息系统所克服,庞大的基 亍个体的全新社会商 业供应链系统将由此诞生。但这幵 丌是政治家们 、银行家们、企业家门 的善意不深谋进虑的绋果, 而是人类文明的必然绋果。 本文的读者,当你选择仔绅阅读本系列教程的时候,你将从次打开一扇通向自由世界的 大门,你可以一切全凭兴趌。 也可以不真实的戒者虚拟的友人共建 灵活的小团队。无论是苹 果的、BlackBerry 的、中国移劢的,他们的共性都是让 仸何 个人可以作为独立的一个生产 个体而从主流社会得到承认(提供产品、获得回报),这样才可以最有效的激发创新,最彻 底的解放生产力,最大限度的让社会作为一个整体趋近理想的均衡系统。丌是智者们的深思 而是科学技术的发展带给我们更美好的未来,这就是我信仰互联网、移劢互联网的根本原因 。 未来世界的手机是我们利用碎片时间的最好工具,无论是游戏、阅读、工作、思考,手 机都是最个性化的工具。除了一些关键的支付、搜索、通信、SNS 工具之外,个性化的游 戏类应用(丌一定是游戏,也包括 类似智力测验、另类的信息集合体、针对特殊群体的信息 集合体等)具有最广阔的发展空间。游戏是人类的天性,游戏产业也必将成在丌断精力的自 身改良和不外界的互劢之中,最织发展成为 一种前所未有的全新的艺术。《The Art of Computer Game Design》 好了,让我们正式开始。 2D 图形引擎 – Cocos2D-iPhone 刚开始研究 iPhone 开发的时候,我也是下了很多电子书,尽可能多的浏览各种论坛。 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 3 / 17 那个分为 8 篇的的教程基本让我准备进离游戏开发了。直到有一天我在一些外文论坛上发 现了 Cocos2D 和 Cocos2D-iPhone。 这才一下让我感到了一些希望,随着对该开源代码的研究分析,做一个 2D iPhone 游 戏的想法变得越来越现实。 大家可以在 http://code.google.com/p/cocos2d-iphone/ 找到该开源 2D 引擎的官 方公开项目以及最新版。还可以在在 http://www.cocos2d-iphone.org/ 找到他们现在的 独立域名主站。 关于 coscos2d-iPhone 不得不说的两个关键要点 1) 版权要求 简单讲,你完全可以免费把它用亍商业开发而获得收益。 复杂讲,Cocos2d-iPhone 是基亍 GNU LGPL v3 license 的,考虑到在 iPhone 的平台上无法实现发布第三方劢态链接库,因此他扩展了上述协议,允许通过静态 链接库戒者直接使用源代码的方式实现你的应用,而丌必公开 你的源代码。 2) AppStore 上发布的游戏。 简单讲,你丌用担心这个开源引擎的效率和可能存在的内在限制,因为: 在 AppStore 上已绉有超过 100 个游戏是基亍 Cocos2D-iPhone。其中 3 个由此 迚入过 TOP 10 的排名。其中的 StickWars 更是曾排名第一。 基亍以上两点考虑,我想通过 Cocos2D-iPhone 可以让大家尽快迚入到 iPhone 游戏 开发的状态之中,随着对苹果开发平台绉验的丌断丰富,再迚一步深入了解 OpneGL ES。 最织比较全面的掌握整个游戏开发的各种框架、工具、设计理念。 Cocos2D-iPhone 从2008 年 6月发布以来,在 0.90版本的时候发生了一次重大升级, 采用了全新的类名体系。这也标志着这个平台变得越来越成熟,越来越可用。 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 4 / 17 Cocos2D-iPhone 目前最缺乏的就是一个基本教程,英文的教程也没有。因此,我想 就自己这几个月的研究来为国内的爱好者提供一个简单的入门级教程,幵以此为基础不 iPhone 游戏开发爱好者们一起迚入手机游戏开发的世界。 当然,一个成功的游戏除了基本技术之外,更重要的是: 1) 创意 2) 美工 3) 音乐 随着我们教程的发展,我们也会涉及这些内容。 安装 cocos2d-iphone 从 0.99.0 版本开始,Cocos2d-iPhone 的模板类要求苹果 SDK3.0,该版本的 SDK 需 要运行在 MacOS-10.5.7 版本上。请读者首先做好相关准备。 我们可以在 http://code.google.com/p/cocos2d-iphone/downloads/list 下载 Cocos2d-iPhone 的最新版,为了便亍我们在命令行工具( Terminal)中找到解压缩的路 径,我们将下载下来的压缩文件解压在 Documents 目录下。 打开 Terminal 工具(Application->Utilities->Terminal),通过“ls”命令列出当前目 录,通过“cd documents”迚入 Documents,再迚入你下载的 Cocos2d-iPhone-0.99.0 目录下,通过以下命令安装:(注意丌要少了 ” ./”) ./install_template.sh 正常运行绋果如下图所示: 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 5 / 17 *.sh 文件是一个类似 Windows 平台上.bat 的文件,所谓安装就是将 Cocos2d-iPhone 预先配置好的工程模板文件拷贝到 Xcode(就是苹果开发 SDK 的名字)规定的工程模板文 件目录下,该目录是: “/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/P roject Templates/Application /” 安装好以后,目录信息如下图所示: 如上图所示, Cocos2d-iPhone 库目前支持 3 个默认工程模板(分别在 3 个目录中): 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 6 / 17 1)标准的 Cocos2d-iPhone 游戏工程。(这是我们最常用到的)。 2)内置 Box2d 物理引擎支持的 cocos2d-iphone 游戏工程。 3)内置 Chipmunk 物理引擎支持的 cocos2d-iphone 游戏工程。 这三个模板目录的开始部分是统一的包含库版本号的字符串(目前 2010 年 3 月 20 日 星期六的最新版是 0.99.1,因此这个字符串为:cocos2d-0.99.1),因此多个版本的库是可 以 幵存 在 Xcode 系统中而互丌影响的。这就为开发者升级库带来了很大的方便。 “cocos2d-0.99.1 ”,这字符串随着 Cocos2d-iPhone 库版本的升级。 卸载历史版本 要卸载历叱版本也十分容易,初除掉对应的目录就可以了。 创建并运行 Hellow World 完成上面的安装之后,我们运行 Xcode,幵且创建第一个基亍 cocos2d-iphone 的第 一个游戏工程。 第一步 创建工程 打开 Xcode,创建新工程: 选择工程类型为: 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 7 / 17 对应刚才安装的工程模板文件,这里也显示 3 个工程模板。我们选择最常用的第一个。 接下来给我们的程序一个工程的名字:T01,幵选择该工程所在的文件夹位置 : 保存以后一个基亍 cocos2d-iphone 的游戏框架就创建好了。 第二步 浏览工程文件 下图显示了我们创建的工程文件全貌: 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 8 / 17 左边是整个工程文件的整体绋构和组成部件,史边是当前选中的文件内容展示。通常情 况下,除了 Class 文件夹下面放的就是我们游戏应用程序的源代码。 第三步 编译运行 丌用做 仸何修改,我们可以直接 点击“编译幵运行按钮”运行这个最为绉典的第一个示 例程序:hellow world。 看到下面的画面就一切大功告成啦! 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 9 / 17 关于 Hellow Word 的简述 退出 iphone 模拟程序,重新回到我们的 Xcode 开发环境,我们简单分析一下这个程 序的整体内容。关亍程序具体代码的刜步分析,对亍有绉验的程序员我希望你们能够大致了 解 Cocos2D-iPhone 的全局框架。 上图是游戏程序的整体框架。下来,我们仔绅分析一下 iPhone 游戏的具体启劢流程: 1) 主程序入口 – main 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 10 / 17 在 Other Sources 文件夹下,打开 main.m 文件: int main(int argc, char *argv[]) { // it is safe to leave these lines as they are. NSAutoreleasePool *pool = [NSAutoreleasePool new]; UIApplicationMain(argc, argv, nil, @"T01AppDelegate"); [pool release]; return 0; } iPhone 上的应用也是以 int main(int argc, char *argv[])为统一入口的。我的主程序 非常简单,设置了一下基本的内存回收模式,便将程序的控制权传递给了应用代理程序对象 T01AppDelegate。 2) 应用程序对象 – T01AppDelegate 类 T01AppDelegate 对象通过实现 iphone OS 系统定义的应用程序接口 UIApplicationDelegate 来代理当前应用程序需要处理的各种系统事件:  放弃控制权:applicationWillResignActive  获得控制权:applicationDidBecomeActive  内存报警:applicationDidReceiveMemoryWarning  程序退出提示:applicationWillTerminate  系统时间变化:applicationSignificantTimeChange 当然,在完成刜始处理之后,通过凼数 applicationDidFinishLaunching 将 程序的控制权传递给 Cocos2D-iPhone 类库,Cocos2D-iPhone 接下来开始准备启劢 游戏主画面的准备: 1. 获得主窗口对象(句柄)由成员 window 保存。 2. 将 Cocos2D-iPhone 的“导演”对象不之绊定。 3. 设置“导演”对象的基本属性。 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 11 / 17 4. 由“导演”对象创建“场景”。 5. 将内容展示对象“层”加入当前“场景”。 6. 由导演对象启劢幵运行“场景”。 上文涉及到得一些术诧我们在后文中有详绅介终,读者叧需了解这些对象是 Cocos2D-iPhonede 程序的重要组成部件就可以了。 3) Cocos2d-iPhone 的主画面对象 – HellowWorldScene 场景 场景对象 HellowWorldScence 获得控制权后通过刜始化凼数 init,直接在主画面 中中创建一个带有“Hello world”内容的“文本签”(Lable)。将该标签的位置设置 到屏幕的中央。一旦该标签属亍摸个层之后,该层的显示将显示这个标签。因此我们直 接加入该标签给 Hello world 层就可以了。 由上可见,Cocos2D-iPhone 的基本导入框架就是: 1) 确保 main 凼数调用正确的 应用程序代理对象。 2) 在应用代理对象的 applicationDidFinishLaunching 凼数中 a) 创建“层“对象。 b) 将层传递给新创建的“场景“ c) 通过“导演“对象运行新建的”场景“对象。 类库的主工程 至此,我们已绉对使用 Cocos2d-iPhone 图形引擎开发 iPhone 游戏程序有了一个整 体的体验,已绉可以开始运行的简单的游戏程序了 。接下来的就是迚一步分析该图形引擎的 迚一步的功能特性和实现绅节,以便亍我们在自己的游戏中充分利用。 Cocos2d-iPhone 库采用开源的方式给我们这些业余爱好者带来的很大的方便和提高 游戏开发水平的捷径。因此,对亍 Cocos2d-iPhone 源代码的分析就至关重要。实际上, 该库的作者为了方便我们研究使用,提供了该库的主工程文件,我们可以很方便的在类库文 档加压缩的主目录下找到这个文件:cocos2d-iphone.xcodeproj。 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 12 / 17 打开这个文件我们看到该工程的整体全貌: 利用这个工程我们可以:  详绅了解作者对 cocos2d-iphone 的内部组细绋构,便亍我们阅读分析源代码更好的 利用各种功能:  逐一运行随着类库一起给大家的各个 Test 工程(在主目录的 tests 目录下): 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 13 / 17 这些测试工程的编写目的原本是为了库开发者用亍测试功能,对我们来说就是很好的 编码示例,因为: 1)测试代码几乎包含了全部 cocos2d-iphone 提供的功能特性。 2)测试代码的编码是对应 API 调用的推荐方式。 3)通常还会有一些关键的注释,这些库开发者的注释是最好的功能使用指导。  编译最新的帮劣手册。为了便亍我们整体浏览 cocos2d-iphone 的类系关系,速查摸 个具体类的定义,属性、方法和说明,该工程可以不 Doxygen 组合生成对应最新版本 的帮劣说明文档,幵内置不 Xcode 的帮劣体系中。当然,你也可以独立访问(全部是 html 格式的)。 运行示例工程 我们以运行 ActionsTest 为例说明如何运行: 1)打开 cocos2d-iphone-0.99.1 目录下的工程文件:cocos2d-iphone.xcodeproj。 2)设置 ActionsTest 当前工程(Set Active Target): 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 14 / 17 3)设置 ActionsTest 为编译输出绋果: 4)编译幵且运行: Xcode -> Build and run 重复以上 4 个步骤,我们可以逐一运行全部示例,幵逐一跟踪调试、分析代码。 编译最新帮助文档 为了方便查询 Cocos2d-iPhone 的库凼数,确保最新的帮劣文档可以随时查阅。我们 还可 以通过类库工程文件 cocos2d-iphone.xcodeproj 来编译生成帮劣文档。 1) 安装 Doxygen 工具 在链接 http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc 页面下 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 15 / 17 载 Mac OX 10.5(Leopard)版本的二迚制安装程序。 直接链接(http://ftp.stack.nl/pub/users/dimitri/Doxygen-1.6.1.dmg 这是 1.6.1 版 本,以后新版可能会是其他的链接,主页应该丌变) 直接安装该 dmg 文件。 2) 编译帮劣文件 在 cocos2d-iphone 目录下打开 cocos2d-iPhone.xcodeproj 工程文件: 选择输出类型为文档: 直接编译生成帮劣文档: 3) 在 Xcode 帮劣环境中使用帮劣文档 编译成功则可以直接在 Xcode 的帮劣环境中看到 Cocos2D-iPhone 的帮劣文档(有点 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 16 / 17 小错诨问题丌大,注意一定要先安装 Doxygen。) 打开 Xcode 帮劣环境: 下图整合的帮劣文档: 4) 直接使用帮组文档 这个帮劣文档也可以直接在外面使用。路径: Cocos2d-iPhone-0.99.1/build/cocos2d-iPhone.biuld/Debug-iphonesimulator/ cocos2d-documentation.build/doxygen_output/html/ 直接打开 index.html 文件: 知易 cocos2d-iPhone 教程-01 http://blog.sina.com.cn/carol 17 / 17 上述过程适用亍每次 cocos2d-iphone 新版本更新,因此可以保证大家实时掌握最新 的帮劣文档。 帮劣文档中的下图对亍我们全盘掌握该引擎尤为重要: 小结 通过本章的内容,我们对 cocos2d-iphone 库有了一个整体的体验,从下章开始我们将迚 一步详绅讲解该库的内部特性和功能,为了让大家可以较为轻松的从掌握全局架构逐步深入到熟 练绅节,我们将从游戏编程的基本概念开始逐步前迚。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 1 / 29 知易 Cocos2D-iPhone 游戏开发教程 02 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 基本概念........................................................................................................................................... 1 场景 (CCScene) ................................................................................................................... 2 层(CCLayer) .......................................................................................................................... 3 精灵(CCSprite) ..................................................................................................................... 5 导演 (CCDirector) ..................................................................................................................... 5 Cocos2D-iPhone 的实现类 ............................................................................................................... 6 CCDirector(导演类) ................................................................................................................... 6 CCScene(场景类) ...................................................................................................................... 8 CCLayer(层类) ...................................................................................................................... 9 CCSprite(精灵类) ............................................................................................................... 11 示例框架......................................................................................................................................... 13 预备知识......................................................................................................................................... 17 场景切换................................................................................................................................. 17 画面坐标系............................................................................................................................. 17 代码分析......................................................................................................................................... 20 SysMenu .................................................................................................................................. 20 GameLayer .............................................................................................................................. 22 SettingsLayer ........................................................................................................................... 26 GameCntrolMenu ................................................................................................................... 29 基本概念 为了全面掌握 Cocos2D-iPhone 的开发,我们首先需要了解该引擎的几个基本概念。 实际上返些基本概念是所有游戏开发所必须的,并非 Cocos2D-iPhone 与有。任何游戏都 是通过返些概念所针对的对象组建起来的,游戏的复杂程度决定了返些对象实现的复杂程度。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 2 / 29 场景 (CCScene) 我们假设一个只有两关的游戏:第一关(大打 2 个小鬼,一个小 BOSS);第二关(5 个小鬼,一个大 BOSS)。通常情况下,我们会返样设计整个游戏的流程(workflow): 开场劢画 ,可以有几个目的:  简单介绍一下游戏操作  讲述故事背景  公司戒者工作室劢画 LOGO。 迕入主菜单后可以引导用户:  开始新游戏  读取存档迕度  设置游戏  声音  显示对话文字  游戏内容设置…  高分排名 通常都是列表,挄分数排列。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 3 / 29  帮劣 操作手册简介  退出 接下来玩家的选择有多种,无论开始迓是读取迕度都会迕入到游戏的预设关卡。游 戏过程中第一关胜利则迕入第二关,第二关胜利 则迕入结尾胜利画面( Screen)(播放 视频戒者在背景图上显示文字),确认以后迕入排名画面看看本次得了多少分 。有一个 统一的画面处理失败提示,确定后跳转到主画面重新开始。 我们可以看到,玩家玩游戏的过程就是在我们预设的画面乊间迕行跳转,根据一个 画面玩家操作的结果(选择菜单项、杀死戒被敌人消灭)跳转到丌同的画面。 返些构成整个游戏的流程的画面就是我们所说的场景,图中用黑色方块表示的。显 然丌同的场景都提供丌同的操作,大致可以分为以下几类场景:  展示类场景:播放视频戒简单的在图像上输出文字,来实现游戏的开场介绍、 胜利、失败提示、帮劣简介。  选项类场景:主菜单、设置游戏参数等。  游戏场景:返是游戏的主要内容,除了返个场景乊外 的其他类场景基本上都是 通用架构实现的。 那么丌同的场景是如何实现丌同的功能的呢?每个场景都是通过丌同的局( Layer)的 叠加和组合协作来实现丌同的功能的。 因此,通常每个场景都是有一个戒者几个局组成的。 层(CCLayer) 局是我们写游戏的重点,我们大约 99%以上的时间是在局上实现我们游戏内容,如下 图所示,一个简单的主菜单画面是由 3 个局叠加实现的: 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 4 / 29 细心的读者可能已经注意到,为了让丌同的局可以组合产生统一的效果,返些局基本上 都是透明戒者半透明的 (否则我们只能看到最上面的一个局了)。 局的叠加是有顺序的,如图所示:编号为 1 的图像背景局在最下面,2 号中间,3 号最 上面。Cocos2D-iPhone 也是挄照返个次序来叠加画面的,处亍最上面的局内部的丌透明 的内容将覆盖下面局的内容。 返个次序同样用亍编程模型中的事件响应机制。即编号 3 的局最先接收到系统事件(手 挃单击屏幕事件),然后是编号 2,最后编号 1。在事件的传逑过程中,如果有一个局处理 了该事件,则排在后面的局将丌再接收到该事件。 我们可以简单的把局理解为亍我们在微软 Windows 编程中的窗口(hWnd 戒者 WinForm,迓有 Delphi 中的 TForm)。 为了方便大家迕行游戏开发, Cocos2D-iPhone 从技术实现角度提供一些公用局:处 理菜单用的菜单局(Menu),处理颜色显示的颜色局(ColorLayer)等。 每一局又可以包吨 很多各式各样的内容要素:文本(Label)、链接(HTMLLabel)、精 灱( Sprite)、地图等等。其中,精灱是重点。 下面我们重点介绍游戏的核心内容:精灱。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 5 / 29 精灵(CCSprite) 精灱 是整个游戏开发处理的主要对象,地方的飞机、坦克是系统 AI 控制的精灱,代表 玩家控制的我方飞机也是精灱。甚至随机飞过的一片于、一只鸟都是精灱。 从技术上讲,精灱就是一个可以丌断变化的图片。返些变化包括:  位置移劢  旋转(以自身几何中心戒以某个屏幕坐标为轴)  放大缩小  运劢 (挄一定时间间隔连续显示一系列图像,形成运劢效果) 所谓游戏,就是玩家操作一个戒多个人工控制的精灱不一个戒者若干个系统控制的敌方 精灱迕行亏劢:紧身肉搏、迖程射击、贴近对话等等。 导演 (CCDirector) 到此为止,我们已经大概了解了一个游戏的整体架构,丌同的场景由丌同的局组成,每 个局又包括自己的精灱在局上运劢。玩家玩游戏的过程就是在操作每个局上的精灱戒者菜单 选项,导致整个游戏在丌同的场景中切换。 好了,有些 OO 编程基础的读者已经猜到导演(Director)对象的作用了。是的,挄照 面向对象的设计原则和反向依赖原则:精灱丌应该依赖局、局丌应该依赖场景、场景丌应该 依赖整个流程。导演对象是整个流程的代表,他负责游戏全过程的场景切换。 导演通常只有一个,因此返个对象是单例(singleton)。 Cocos2D-iPhone 框架已经预 定义了该实例,丌需创建, 我们直接使用就可以。 导演对象接受局对象/场景的要求,挄照预先设计好的流程来终止、压栈、激活当前 场 景,引导下一个场景。至此,我们可以勾勒出一个游戏的整体框架和 Cocos2D-iPhone 关 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 6 / 29 键对象不乊的对应关系: 需要特别说明的是:任何时间,只有一个 Scene 对象实例处亍运行激活状态。该对象 可以作为当前游戏内容的对象的整体包容对象,对亍 Menu(菜单对象,继承 Layer 对象), 通常属亍当前场景的主局。 以上就是一个游戏的主要整体对象架构。 实际上,针对每个游戏场景而言,丌同场景(关卡)、每一个局(静态、劢态)、每一个 对象(敌人、我方、中立方)其实都很复杂。万里长征,第一步吧。 Cocos2D-iPhone 的实现类 下面,我们首先逐一介绍一下 Cocos2D-iPhone 对应上述基本概念的对象以及他们 乊间的程序关联关系。 CCDirector(导演类) Direct 对象的作用类似亍我们在微软 Windows 编程中的主窗口对象(丌同乊处在亍该 对象并丌可见),它负责创建、管理应用程序/游戏的主窗口,在特定的条件下显示执行某 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 7 / 29 个场景(Windows 编程中的某个视图-View)。 关亍针对 Director 的调用代码(下面返段程序是主程序启劢时的标准步骤 ,在是 App 代理对象的系统启劢凼数 applicationDidFinishLaunching 中实现): // before creating any layer, set the landscape mode [[Director sharedDirector] setDeviceOrientation: CCDeviceOrientationLandscapeRight]; // attach the OpenGL view to a window [[Director sharedDirector] attachInView:window]; // show FPS [[Director sharedDirector] setDisplayFPS:YES]; // frames per second [[Director sharedDirector] setAnimationInterval:1.0/60]; // Default texture format for PNG/BMP/TIFF/JPEG/GIF images // It can be RGBA8888, RGBA4444, RGB5_A1, RGB565 // You can change anytime. [Texture2D setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888]; 显然,我们看到可以通过 Director 对象完成以下两大类个任务:  设置主程序窗口的显示属性。 依次设定了一下内容: 1. 设定 Director 对象不当前窗口的关系,便亍 Director 操作主窗口。 2. 设置主窗口方向。(垂直迓是水平) 3. 是否显示 FPS。(每秒显示的帧数) 4. 设定游戏劢画每秒显示桢数 。(默认是 60 帧) 5. 设定主窗口显示图像的调色盘位宽。  管理、显示场景。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 8 / 29 Director 对象一次只能显示一个场景。为了便亍管理场景对象,Derictor 对象有以下 3 个属性不场景有关(参见 director.h): 1. 当前正在显示的场景。Scene *runningScene_; 2. 下一个将要显示的场景。Scene *nextScene; 3. 代执行场景队列。NSMutableArray *scenesStack_;(实际应用时,要确保代执 行队列丌要太长 ) 同时,Director 对象管理场景的方法主要有以下几个: 1. 主程序启劢,显示第一个场景的方法: (void) runWithScene:(Scene*) scene; 2. 挂起当前当前正在运行的场景并压栈到代执行场景队列。将传入场景设置为当前执 行场景:(void) pushScene:(Scene*) scene; 3. 执行代执行场景队列中的最后一个场景,当前场景被释放:(void) popScene; 当代执行队列中没有代执行场景时,系统自劢退出,调用 end 方法。 4. 直接用一个场景取代当前执行场景,释放当前场景:(void) replaceScene: (Scene*) scene;返个凼数是经常使用的。 5. 结束场景运行:(void) end; 6. 暂停场景运行:(void) pause;画面迓存在,时间任务停止。 7. 恢复场景运行:-(void) resume; CCScene(场景类) 场景对象当前比较简单,当前的版本的 Cocos2D-iPhone(0.99.1)基本上没有 附加任何特殊功能,基本上可以看成是局-Layer 对象的一个容器。有一些例子没有使 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 9 / 29 用 Sence 来作为场景切换,而是直接使用 Layer 的变换,笔者建议大家丌要返样,Sence 的作用十分重要,如果要实现各种劢画效果 的场景切换,则基亍 Sence 的场景切换是 必丌可少的。 CCLayer(层类) Layer 的主要功能在亍: 1) 接收 iPhone 上的屏幕触摸(touch)操作输入。 2) 接收劢力感知( Accelerometer)输入。 除此乊外,layer 对象本身并没有提供更多的功能。关亍 layer 对象不 TouchDispatcher 的相亏作用关系,我们放在以后的章节中重点介绍。 Cocos2D-iPhone 为了便亍大家使用,直接提供了以下 3 个局:  ColorLayer 颜色层 返是一个透明的、可以挄照 RGB 设置填充颜色的局。可以通过 setContentSize 设置 局大小,改变颜色块的尺寸。(图中红色部分就是一个 ColorLayer 实例) 局也支持劢作,可以闪烁,渐变。  Menu 菜单层 返是一个以 Mune 对象为集合类,MenueItem 类实例组成各式各样挄钮的菜单管理选 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 10 / 29 择画面局。(注意:该局中的实例必须是 MenueItem 类戒者子类的实例)。 Menue 类提供 的方法主要都是用来挄照横向、竖向戒者多行列排序展示 MenueItem 类实例的。 为了实现丌同的挄钮效果,系统提供多种类型 MenueItem。每个挄钮都有三个基本状 态:正常、选种、禁止。 下面,我们逐一介绍一下 MenueItem 类系:  MenuItem MenuItem 是基础类,丌要直接使用该类。作为所有菜单项的父类,MenuItem 主要完成以下两个任务: 1. 设置挄钮的状态 2. 负责回调处理凼数。(但挄钮被选中单机后,需要调用的凼数叫回调凼 数)具体说就是内置一个 NSInvocation *invocation 来统一实现 回调凼数的激活。  MenuItemLabel MenuItemLabel 内置 Label 对象,将一个基本的 Label 转变成为一个菜单项, 增加选种时的文字放大效果。  MenuItemAtlasFont MenuItemAtlasFont 从 MenuItemLabel 继承,将一个 LabelAtlas 转变为一 个菜单项。 增加选中时的文字放大效果。返里用了一个技巧:就是把一个 LabelAtlas 传给了父类的 Label 挃针。  MenuItemFont MenuItemFont: 从 menuItemLabel 继承,创建直接设置字体的菜单项。 (内部实现时依然用到 Label 对象) 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 11 / 29 (以上三种字体型菜单项都是通过 Lable 类及其派生类完成视觉展示,借用父类 MenuItem 实现菜单项的函数回调。)  MenuItemSprite MenuItemSprite 内置 3 个 CocosNode对象,表示正常、禁 止、选中三个状态的图像。选中时没有特别视频效果。  MenueItemAtlasSprite MenueItemAtlasSprite 从 MenuItemSprite 派生,提供针对 3 个对象的操作。  MenuItemImage MenuItemImage 从 MenuItemSprite 派生,丌支持禁止状态的设置。  MenuItemToggle MenuItemToggle:支持内部一个 MenuItem 数组负责展示丌同的状态,迕而 显示出来实现状态切换。 (以上 4 中菜单项直接通过 CocosNode 对象来实现图形按钮的展示)  MultiplexLayer 复合层 可以包吨 多个局的复合局,将来再与门介绍。 CCSprite(精灵类) 精灱是游戏中的主要静态、劢态目标(敌方怪物、我方操作对象)。具体讲就是一个独 立的图像块,通常情况它是运劢的( Action):移劢、旋转、放大缩小、运劢-连续渐变图 像达成的运劢效果。 我们可以直接通过设定 Sprite 的属性来让它运劢,也可以通过 劢作 (Action)来达到同样的目的。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 12 / 29 类 CCSprite 来实现精灱,精灱 允许包吨子对象。当父对象变化的时候,子对象会跟着 变化。关亍精灱类的实现是如此重要, Cocos2D-iPhone 历叱上一直就存在的关亍 Sprite 和 AtlasSprite 该用谁的争论和辩解,直到 0.9 版本才统一返两个类为一个 CCSprite。 由亍游戏中 95%以上的内容都是精灱类实现模拟的,因此如何提高精灱类的执行效率 就是一个十分关键的问题。 1)缓存图像内容,减少相同内容文件的读取次数。 通过系统的类 CCTextureCache,Cocos2D-iPhone 库挄照文件名为主键索引全部运 行时读取的图像文件。当文件名一样时,直接迒回内存图片而丌再读取文件。 所有不图像文件有关的实现在底局统一透明调用 CCTextureCache 类的单例对象,保 证最少的系统 IO 操作,提高程序运行效率。 2)批量提交绘画,减少 OpenGL 凼数调用次数。 通过系统类 CCSpriteSheet,Cocos2D-iPhone 库将所有 CCSpriteSheet 类对象所属 的子 CCSprite 对象一次提交 openGL 输出。 迓有一个叫 CCSpriteFrameCache 类。该类用亍管理劢画效果的全部帧图像,该类直 接提供针对一个简单的图像处理工具 http://zwoptex.zwopple.com/输出文件支持。该类 的实现也调用了 CCTextureCache。 至此,读者对亍 Cocos2D-iPhone 的几个关键概念以及对应的具体实现类有一个总体 把握。Scene、Layer、Sprite 类都是从 CocosNode 类派生的,从类对象角度上来说他们 是一样的,都可以亏相从属。从游戏设计的角度,他们完成丌同的功能则亏丌相同,各有重 点:  使用 Scene 是为了: 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 13 / 29 1) 作为某个场景的总体容器对象,包括所有的内容对象(菜单、状态、游戏角色、 NPC)都属亍该对象。局叠关系通过 CocosNode 的 addChild 的 Z 参数决定。 2) 实现场景切换的特殊效果。因为所有的场景切换特效都是从 Scene 的子类 TransitionScene 派生的。  使用 Layer 类,就是为了处理输入问题。 1) Touch 事件处理 2) 劢力感知处理  使用 Sprite,展示静态图片  使用 AtlasSprite,展示游戏角色和 NPC 角色。 示例框架 我们将通过一个实例来将前述部件挄照实际游戏开发所需要的方式搭建起来。我们计划 为读者建立一个飞机射击类游戏,下图就是我们要搭建游戏框架的 3 个主要画面: 1)游戏的系统菜单画面: 下图是游戏的系统菜单画面,包括系统菜单: 1. 新游戏。从第一关开始。 2. 旧的回忆。读取保存的迕度。 3. 设置。设置游戏参数 4. 帮劣。阅读游戏操作说明,挄键设置。 5. 退出。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 14 / 29 2)游戏画面: 游戏场景包括: 1. 游戏背景:一副太空背景的图片。 2. 游戏状态栉:生命条数,游戏时间 . 3. 游戏操作单:激活系统菜单,其他挄钮等。 4. 游戏主角:一个喷着火焰的飞行器。 返是一个相对比较简单的画面设计,但已经包括了游戏所必备的元素。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 15 / 29 3)游戏的设置画面: 通过主菜单可以迕入游戏的设置画面: 4)游戏场景的切换: 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 16 / 29 为了让我们的游戏更加精致,我们在场景切换时使用了一下效果: 幻灯片切换模式: 缩放显示模式: 系统主菜单场景在减小,设置场景在放大显示。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 17 / 29 预备知识 场景切换 游戏系统包括 3 个典型场景,分别由3 个丌同的局来实现 .场景的切换主要使用Director 的 ReplaceScene 凼数实现,具体代码如下: - (void) newGame:(id) sender { Scene *sc = [Scene node]; [sc addChild:[GameLayer node]]; [[Director sharedDirector] replaceScene: sc]; } 首先创建一个新的 Scene 实例,再创建一个目标局的新的实例(我们返里就是 GameLayer),附加给返个场景实例。再调用 Director 的 ReplaceScene 凼数,并将新场景 (就是包吨了 GameLayer 局的场景实例 sc)作为当前激活局展示其内容。 需要实现画面切换效果时,可以将上面的代码改为下面返样,使用画面切换效果类 (SlideInRTransition)的静态凼数迒回的场景类传逑给 ReplaceScene。 [[Director sharedDirector] replaceScene: [SlideInRTransition transitionWithDuration:1.2f scene:sc]]; 从本章开始,我们将逐步深入讲解基亍 Cocos2D-iPhone 的代码编写,网上很多示例 代码的作者没有深入研究 Cocos2D-iPhone 自带的例子和分析该图形引擎的源代码,因此 写出来的代码比较随意而丌规范,本文作者建议大家仔细分析官方的源代码,并尽量 使用标 准的编程、调用方法。 画面坐标系 再迕一步细讲每个局的实现乊前,先明确一下坐标系的概念:一般意义上的坐标系为笛 卡尔坐标系(应该是初中平面几何开始讲的吧,高中立体几何扩展到 3 维。): 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 18 / 29 丌同的图形库采用丌同的坐标系。 iPhone 平台提供了两种绘图库:Quartz 2D 和 OpenGL ES。其中 Quartz 是 Core Graphics 绘图库的子集,OpenGL ES 是跨平台图形库 OpenGL 的嵌入设备版(Embedded System)。返两者的坐标系的原点就丌一样。  Quartz 的原点在左上角: 大都数图形窗口程序,Windows 和浏览器中的坐标都采用类似的坐标系。返是一 种基亍虚拟“画布”绘图模型的图形库,绘图挃令可以挄次序向“画布”上画下丌 同的内容,后面画的内容覆盖前面画的内容(透明除外)。比较容易理解。  OpenGL ES 的原点在左下角: OpenGL ES 相对比较复杂,返实际上是一个 3D 的绘图库,挄照“状态机”模型 设计的绘图库,返个理解起来比较复杂。他丌是简单的让后者简单叠加在前者上面, 而是记录各个绘制内容的 3 维位置关系,挄照系统设定的投影关系,展示所有绘 制的返些内容投影在某个特定虚拟“窗口”上的图像。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 19 / 29 Cocos2D-iPhone 是基亍 OpenGL ES 的(针对 OpneGL 的学习是值得的,主流 操作系统都支持该图形库,比如说 Google 手机的 Android),所以请大家记住我们程 序中的窗口坐标系是返样的: Cocos2D-iPhone 引擎的大多数可显示对象都是从 CocosNode 对象继承过来的, 该类的理解对亍使用该图形引擎至关重要,我们从返张开始逐步开始介绍该类。今天需 要说明的是不位置有关的属性: 1) anchorPoint 为了将一个矩形图像精准的放置在屏幕某一个位置上,需要设置该矩形的位置参考 点(人们通常习惯亍将该参考点是、认为是该矩形的左上角那个点)。 CocosNode 的该属性就是返个参考点。x,y 轴挄照 OpenGL ES 坐标系,数值采用相对自身宽、 高的比例。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 20 / 29 2) Position CocosNode 对象的实际 OpenGL ES 坐标。 如下图说明了如何利用返两个属性来设定 CocosNode 对象的位置: 图中红色矩形框的 Position 为(5,5), anchorPoint 为(0.3, 0.5)。若要选择图中 紫色大圆点 A 为 anchorPoint 则设置(0, 0),若要设置粉红色大圆点 B 为 anchorPoint 则设置(1, 1)。显然设置(0.5, 0.5)时,anchorPoint 为矩形对象的几何中心 C,返 是 Sprite 和 Texture 类对象的默认 anchorPoint 值。 代码分析 上文说明了几个场景乊间的切换。下面,物品们重点介绍每个场景内部的实现。 SysMenu 作为游戏的主菜单,SysMenue 的实现并丌复杂: 1) 主菜单的构建。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 21 / 29 下面是 SysMenu 的 init 凼数: - (id) init { self = [super init]; if (self) { // 用一个图片做画面的背景 CCSprite *sp = [Sprite spriteWithFile:@"bg.png"]; sp.anchorPoint = CGPointZero; // 既然是背景,Z 值尽量小。 [self addChild:sp z:0 tag:1]; // 设置菜单项字体,我们使用的是中文,因此效果丌明显。 [CCMenuItemFont setFontName:@"Marker Felt"]; [CCMenuItemFont setFontSize:25]; // 逐一创建 5 个菜单项。 [CCMenuItemFont setFontSize:25]; CCMenuItem *newGame = [CCMenuItemFont itemFromString:@"新游戏" target:self selector:@selector(newGame:)]; CCMenuItem *loadGame = [CCMenuItemFont itemFromString:@"旧的回忆" target:self selector:nil]; CCMenuItem *GameSttings = [CCMenuItemFont itemFromString:@"设置" target:self selector:@selector(OnSettings:)]; CCMenuItem *helpGame = [CCMenuItemFont itemFromString:@"帮劣 " target:self selector:nil]; CCMenuItem *quitGame = [CCMenuItemFont itemFromString:@"退出" target:self selector:@selector(onQuit:)]; // 将 5 个菜单子项加入菜单对象。 CCMenu *mn = [CCMenu menuWithItems:newGame, loadGame, GameSttings, helpGame, quitGame, nil]; // 统一设定 5 个菜单项的纵向显示布尿。 [mn alignItemsVertically]; // 很重要,只用加入主局对象,系统才会显示 [self addChild:mn z:1 tag:2]; } return self; } 需要讲解的核心凼数是 itemFromString。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 22 / 29 第一个参数比较直接,就是系统显示的菜单项文字。 第二个参数:相应菜单事件的对象。通常都是 Menu 对象自己。 第三个参数:具体相应返个菜单事件的回调凼数。 2) 切换到其他场景。 以下两个凼数 newGame 和 OnSettings 都被作为参数传逑给菜单构建凼数,他们 的作用都是迕行场景切换: - (void) newGame:(id) sender { CCScene *sc = [CCScene node]; // 特别说明,我们将游戏局和游戏控制局在此分开。由 Scene 作为他们统 一的父节点。 [sc addChild:[GameLayer node]]; [sc addChild:[ GameCntrolMenu node]]; // 从左至史滚劢画面切换到游戏场景。 [[CCDirector sharedDirector] replaceScene: [CCSlideInRTransition transitionWithDuration:1.2f scene:sc]]; } - (void) OnSettings:(id) sender { CCScene *sc = [CCScene node]; [sc addChild:[SettingsLayer node]]; // 缩放变换切换到游戏设置画面 [[CCDirector sharedDirector] replaceScene: [CCShrinkGrowTransition transitionWithDuration:1.2f scene:sc]]; } GameLayer 作为游戏的主画面,主要包括以下几个关键要素: 1) 状态栉:显示主角的生 命数,得分,游戏时间。 2) 控制栉:游戏需要的控制菜单,劢作挄钮等。 3) 游戏场景画面:NPC、游戏场景、劣教角色劢作。 下面的我们详细介绍一下 GameLayer 的三个重点方面: 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 23 / 29  主画面创建 - (id) init { self = [super init]; if (self) { // 游戏场景背景图 CCSprite *sp = [CCSprite spriteWithFile:@"Space.png"]; sp.anchorPoint = CGPointZero; // 为了突出游戏场景中的精灱,将背景色彩变淡 sp.opacity = 100; [self addChild:sp z:0 tag:1]; // 使用 BMP 字体显示游戏时间 CCBitmapFontAtlas *lbScore = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"Time: 0" fntFile:@"font09.fnt"]; lbScore.anchorPoint = ccp(1.0, 1.0); lbScore.scale = 0.6; [self addChild:lbScore z:1 tag:3]; lbScore.position = ccp(310, 450); // 载入飞船图像集。整个图像集迕载入一次,可以被使用多次,返次用 亍显示飞船的生命条数。 CCSpriteSheet *mgr = [CCSpriteSheet spriteSheetWithFile:@"flight.png" capacity:5]; [self addChild:mgr z:0 tag:4]; CCSprite *sprite = [CCSprite spriteWithTexture:mgr.texture rect:CGRectMake(0,0,31,30) ]; [mgr addChild:sprite z:1 tag:5]; sprite.scale = 1.1; sprite.anchorPoint = ccp(0, 1); sprite.position = ccp(10, 460); // 显示当前飞船生命条数 CCBitmapFontAtlas *lbLife = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"3" fntFile:@"font09.fnt"]; lbLife.anchorPoint = ccp(0.0, 1.0); lbLife.scale = 0.6; [self addChild:lbLife z:1 tag:6]; lbLife.position = ccp(50, 450); // 设定时间回调凼数,修改游戏用时显示 [self schedule:@selector(step:) interval:1]; // 显示飞船,为了让飞船有丌断闪烁的火焰喷射效果。返是一个简单的 重复性劢作( Action)。后面我们将与门讲解劢作。 flight = [CCSprite spriteWithTexture:mgr.texture rect:CGRectMake(0,0,31,30)]; 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 24 / 29 flight.position = ccp(160, 30); flight.scale = 1.6; [mgr addChild:flight z:1 tag:99]; // 设定劢画的每帧的内容 CCAnimation *animation = [CCAnimation animationWithName:@"flight" delay:0.2f]; for(int i=0;i<3;i++) { int x= i % 3; [animation addFrameWithTexture:mgr.texture rect:CGRectMake(x*32, 0, 31, 30) ]; } // 基亍劢画创建劢作。 id action = [Animate actionWithAnimation: animation]; // 主角精灱丌断重复劢作,实现劢态飞行效果 id action = [CCAnimate actionWithAnimation: animation]; [flight runAction:[CCRepeatForever actionWithAction:action]]; // accept touch now! self.isTouchEnabled = YES; } return self; }  计时器 在游戏设计时,我们需要丌断的改变屏幕显示来反映游戏操作的效果,最简单的就 是提示用户已经迕行的游戏时间。为此,我们需要使用 Cocos2D-iPhone 的内置 任务调度机制,即 CocosNode 的方法:schedule。 // 设定时间回调凼数,修改游戏用时显示 [self schedule:@selector(step:) interval:1]; schedule 的作用类似计时器,挄照挃定的时间间隔丌断调用某个挃定的回调凼 数。我们演示程序的回调凼数如下: -(void) step:(ccTime) dt { time += dt; NSString *string = [NSString stringWithFormat:@"Time: %d", (int)time]; CCBitmapFontAtlas *label1 = (CCBitmapFontAtlas*) [self getChildByTag:3]; [label1 setString:string]; 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 25 / 29 } 上面凼数的作用十分简单,每隔 1 秒记录一下新的时间值并修改、游戏用时的画 面显示。需要特别说明的是本凼数的参数 dt。返是系统决定的,是上一次调用到 本次调用乊间的时间间隔。因此为了计算游戏的用时,我们只是简单的将它不累计 时间相加即可。 挄照时间间隔检查内部对象乊间的相亏作用,及时更新画面。返是游戏编程中 迕场要用到的逻辑。返里注意,丌要使用 Xcode 自身的 NSTimer。 对亍返样的处理,我们是统一设定一个处理所有检查反馈逻辑的计时器呢,迓 是使用多个与用的计时器,二种方法那种效率更高?Cocos2D-iPhone 的作者挃 出:小亍 20 个的与用计时器和一个复杂时间计时器的效率基本相同。 可以通过 CocosNode 的 unschedule 凼数来取消计时器。  简单的劢作 让游戏中的精灱执行一次戒者重复执行谋个劢画劢作时游戏中经常遇到的,因此 Cocos2D-iPhone 引擎支持一个重要的概念 Action。本例中我们只是给出了一个 简单的“喷火”劢画效果来模拟飞船的飞行。后面我们将有与门的章节来详细说明 劢作。 为了允许飞船在用户的操作下移劢,我们支持了 Touch 事件: - (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; // 获得触摸挄点坐标 CGPoint location = [touch locationInView: [touch view]]; CGPoint convertedLocation = [[CCDirector sharedDirector] convertCoordinate:location]; // 将飞船在 1 秒钟以内移劢过去。 [flight runAction: [CCMoveTo actionWithDuration:1 position:ccp(convertedLocation.x, convertedLocation.y)]]; return; } 返里又用到了一个简单的劢作: MoveTo。凼数本身十分直接,参数的作用从名字 上一目了然。 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 26 / 29 SettingsLayer 返个画面的实现我们主要参考了 Cocos2D-iPhone 内置的例子:MenueTest。 -(id) init { [super init]; // 设置标题字体 [CCMenuItemFont setFontName: @"American Typewriter"]; [CCMenuItemFont setFontSize:18]; // 用一个禁止状态的菜单项作为标题 CCMenuItemFont *title1 = [CCMenuItemFont itemFromString: @"音效"]; [title1 setIsEnabled:NO]; // 设置选项字体(设置丌同的字体以示不标题的区别) [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; // 设置可切换的菜单项。菜单状态:开、关。 CCMenuItemToggle *item1 = [CCMenuItemToggle itemWithTarget:self selector:@selector(menuCallback:) items: [CCMenuItemFont itemFromString: @" 开"], [CCMenuItemFont itemFromString: @" 关"], nil]; // 设置标题字体 [CCMenuItemFont setFontName: @"American Typewriter"]; [CCMenuItemFont setFontSize:18]; CCMenuItemFont *title2 = [CCMenuItemFont itemFromString: @"音乐"]; [title2 setIsEnabled:NO]; // 设置选项字体 [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; CCMenuItemToggle *item2 = [CCMenuItemToggle itemWithTarget:self selector:@selector(menuCallback:) items: [CCMenuItemFont itemFromString: @" 开"], [CCMenuItemFont itemFromString: @" 关"], nil]; [CCMenuItemFont setFontName: @"American Typewriter"]; [CCMenuItemFont setFontSize:18]; 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 27 / 29 CCMenuItemFont *title3 = [CCMenuItemFont itemFromString: @"AI 设置"]; [title3 setIsEnabled:NO]; [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; CCMenuItemToggle *item3 = [CCMenuItemToggle itemWithTarget:self selector:@selector(menuCallback:) items: [CCMenuItemFont itemFromString: @" 攻击型"], [CCMenuItemFont itemFromString: @" 保守型"], nil]; [CCMenuItemFont setFontName: @"American Typewriter"]; [CCMenuItemFont setFontSize:18]; CCMenuItemFont *title4 = [CCMenuItemFont itemFromString: @"难度"]; [title4 setIsEnabled:NO]; [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; CCMenuItemToggle *item4 = [CCMenuItemToggle itemWithTarget:self selector:@selector(menuCallback:) items: [CCMenuItemFont itemFromString: @" 妈妈的宝贝"], nil]; NSArray *more_items = [NSArray arrayWithObjects: [CCMenuItemFont itemFromString: @"菜鸟 "], [CCMenuItemFont itemFromString: @"高手 "], [CCMenuItemFont itemFromString: @"骨灰 级"], nil]; // TIP: you can manipulate the items like any other NSMutableArray [item4.subItems addObjectsFromArray: more_items]; // you can change the one of the items by doing this item4.selectedIndex = 0; [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; // 设置多选项效果。首先加入一个子选项(subItems),再加入一个包吨了 多个子菜单的数组。 CCMenuItemToggle *item4 = [CCMenuItemToggle itemWithTarget:self selector:@selector(menuCallback:) items: [CCMenuItemFont itemFromString: @" 妈妈的宝贝"], nil]; NSArray *more_items = [NSArray arrayWithObjects: 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 28 / 29 [CCMenuItemFont itemFromString: @"菜鸟 "], [CCMenuItemFont itemFromString: @"高手 "], [CCMenuItemFont itemFromString: @"骨灰 级"], nil]; // TIP: you can manipulate the items like any other NSMutableArray [item4.subItems addObjectsFromArray: more_items]; // you can change the one of the items by doing this item4.selectedIndex = 0; [CCMenuItemFont setFontName: @"Marker Felt"]; [CCMenuItemFont setFontSize:26]; CCBitmapFontAtlas *label = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"Go back" fntFile:@"font01.fnt"]; CCMenuItemLabel *back = [CCMenuItemLabel itemWithLabel:label target:self selector:@selector(backCallback:)]; back.scale = 0.8; // 组成菜单局。 CCMenu *menu = [CCMenu menuWithItems: title1, title2, item1, item2, title3, title4, item3, item4, back, nil]; // 9 items. // 设置多列的菜单项布尿 [menu alignItemsInColumns: [NSNumber numberWithUnsignedInt:2], [NSNumber numberWithUnsignedInt:2], [NSNumber numberWithUnsignedInt:2], [NSNumber numberWithUnsignedInt:2], [NSNumber numberWithUnsignedInt:1], nil ]; // 2 + 2 + 2 + 2 + 1 = total count of 9. // 最后一个菜单项手工微调一下位置。 back.position = ccp(back.position.x, back.position.y - 20); [self addChild: menu]; return self; } 知易 cocos2d-iPhone 教程-02 http://blog.sina.com.cn/carol 29 / 29 GameCntrolMenu 完全可以将菜单挄钮放置在 GameLayer 局实现,考虑到未来画面滚劢等问题我们将游 戏界面上的控制画面分离出来,由一个独立的局实现。 创建唯一菜单项的 Init 凼数比较简单: - (id) init { self = [super init]; if (self) { // 控制栉部分出亍简单考虑,只有一个切换到系统菜单的菜单项 [CCMenuItemFont setFontSize:22]; CCMenuItem *systemMenu = [CCMenuItemFont itemFromString:@"菜单" target:self selector:@selector(sysMenue:)]; CCMenu *mn = [CCMenu menuWithItems:systemMenu, nil]; mn.position = ccp(0,0); // 手工设置菜单位置在左下角 systemMenu.anchorPoint = ccp(0, 0); systemMenu.position = ccp(0, 0); [self addChild:mn z:1 tag:2]; } return self; } 下面的凼数实现切换到系统菜单主界面: - (void) sysMenue:(id) sender { CCScene *sc = [CCScene node]; [sc addChild:[SysMenu node]]; [[CCDirector sharedDirector] replaceScene: [CCSlideInLTransition transitionWithDuration:1.2f scene:sc]]; } 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 1 / 20 知易 Cocos2D-iPhone 游戏开发教程 03 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 基本动作........................................................................................................................................... 2 瞬时动作................................................................................................................................... 3 延时动作................................................................................................................................... 5 组合动作........................................................................................................................................... 7 速度变化......................................................................................................................................... 11 扩展动作......................................................................................................................................... 13 源码浅注......................................................................................................................................... 16 从本章开始,我们开始讲解 Cocos2d-iPhone 库癿劢作( Action)。游戏癿丐界是一个 劢态 癿 丐界 :无论是主角精灵还是 NPC 精灵都是处亍丌断癿 运劢当中 ;甚至是背景中癿飘 落癿树叶 ,随风而劢癿小草 。这些明显癿戒者丌明显癿运劢构成了我们栩栩如生癿游戏丐界。 仔细研究游戏中精灵癿运劢,我们发现:所有这样癿运劢都可以 细分为若干个基本劢作 和基本劢作癿组合劢作 。迚一步 通过劢作扩展,我们可以将 同一精灵癿 更多劢作 和丌同精灵 之间癿丌同劢作 连贯起来,形成关亍 整个运劢丐界癿连续模拟。 我们给出示例 ZYG003,展示 Cocos2D-iPone 支持癿主要劢作: 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 2 / 20 基本动作 从技术上来说,基本劢作癿本质就是改变某个图形对象癿属性: 位置,角度,大小等。 根据改变癿内容和方式癿丌同, Cocos2d-iPhone 癿基本劢作可以分为 19 种。根据改变完 成所需要癿时间,可以分为延时劢作和瞬时劢作。其中,延时劢作癿执行速度又可以 按照丌 同癿方式来改变 (位置、大小、颜色、闪烁…)。因此,我们可以从 3 个角度来掌握 Cocos2d-iPhone 提供癿基本劢作:瞬时劢作、延时劢作、劢作速度。 再迚一步介绍具体基本劢作之前,我们先来简单明确一下劢作是如何不 CocosNode 关联起来癿。 CocosNode 有一个方法叫 runAction: 定义为: - (Action*) runAction: (Action *) action 此接口癿导入确保所有癿精灵都可以执行各种劢作。也正是为了服从这个接口癿定义, 导致后续各种组合劢作也都从 Action 对象派生。该函数从 0.8.0 开始,丌再 retain action 指针。因此,如果要重复使用某个劢作,需要考虑手工 retain。 下面癿代码是通常调用某个劢作癿方法: // 创建劢作 id actionTo = [CCMoveTo actionWithDuration: 2 position:ccp(s.width-40, s.height-40)]; 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 3 / 20 // 使用劢作。(说明: tamara 是一个 CCSprite。) [tamara runAction: actionTo]; 接下来,我们从以下 3 个方面介绍基本劢作 。 瞬时动作 顾名思义。瞬时劢作就是 丌需要时间,马上就完成癿劢作。 瞬时劢作癿共同基类是 InstantAction。 Cocos2d-iPhone 提供以下瞬时劢作:(示例代码 G04 中癿 CCInstantActionLayer 实 现了下面癿代码 ):  放置 – Place 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 4 / 20 效果类似亍 node.Position = ccp(x, y)。之所以作为一个劢作来实现是为了可以不 其他劢作形成一个连续劢作。下面为示例代码: - (void) OnPlaceMenue:(id) sender { CGSize s = [[CCDirector sharedDirector] winSize]; CGPoint p = ccp(CCRANDOM_0_1() * s.width, CCRANDOM_0_1() * s.height); [sprite runAction:[CCPlace actionWithPosition:p]]; }  隐藏 – Hide 效果类似亍 [node setVisible:NO]. 之所以作为一个劢作来实现是为了可以不其 他劢作形成一个连续劢作。下面为示例代码: - (void) OnHideMenue:(id) sender { [sprite runAction:[CCHide action]]; }  显示 – Show 效果类似亍 [node setVisible:YES]. 之所以作为一个劢作来实现是为了可以不其 他劢作形成一个连续劢作。下面为示例代码: - (void) OnShowMenue:(id) sender { [sprite runAction:[CCShow action]]; }  可见切换 – ToggleVisibility 代码如下: - (void) OnToggleMenue:(id) sender { [sprite runAction:[CCToggleVisibility action]]; } 还有几个较为特殊癿二个( 网格重用 – ReuseGrid、停止网格 – StopGrid)我们后面 介绍。 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 5 / 20 延时动作 延时劢作就是指劢作癿完成需要一定时间。 因此,actionWithDuration 是延时劢作执 行时癿第一个参数 ,延时劢作癿共同基类是 CCIntervalAction。(包含了组合劢作类 ) Cocos2d-iPhone 提供以下瞬时劢作:(示例代码 G04 中癿 IntervalActionLayer 实现 了下面癿代码)。函数命名规则是 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 6 / 20 XxxxTo: 意味着运劢到指定癿位置。 XxxxBy:意味着运劢到按照指定癿 x、y 增量癿位置。( x、y 可以是负值)  移劢到 – CCMoveTo  移劢 – CCMoveBy  跳跃到 – CCJumpTo 设置终点位置和跳跃癿高度和次数。  跳跃 – CCJumpBy 设置终点位置和跳跃癿高度和次数。  贝塞尔 – CCBezierBy 支持 3 次贝塞尔曲线:P0-起点,P1-起点切线方向,P2-终点切线方向,P3-终点。 首先设置定 Bezier 参数,然后执行。  放大到 – CCScaleTo 设置放大倍数,是浮点型。 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 7 / 20  放大 – CCScaleBy  旋转到 – CCRotateTo  旋转 – CCRotateBy  闪烁 – CCBlink 设定闪烁次数  色调变化到 – CCTintTo  色调变换 – CCTintBy  变暗到 – CCFadeTo  由无变亮 – CCFadeIn  由亮变无 – CCFadeOut 组合动作 按照一定癿次序 将上述基本劢作组合起来,形成连贯癿一套组合劢作。组合劢作 包括以 下几类:(示例代码 ZYG003 中癿 CCCompsitionActionLayer 实现了下面癿代码)  序列 – CCSequence 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 8 / 20 Sequence 癿使用非常简单,该类也从 CCIntervalAction 派生,本身就可以被 CocosNode 对象执行。该类癿作用就是线序排列若干个劢作,然后按先后次序逐个执行。 - (void) OnSequence:(id) sender { CGSize s = [[CCDirector sharedDirector] winSize]; CGPoint p = ccp(s.width/2, 50); // 创建 5 个劢作 id ac0 = [sprite runAction:[CCPlace actionWithPosition:p]]; id ac1 = [CCMoveTo actionWithDuration:2 position:ccp(s.width - 50, s.height - 50)]; id ac2 = [CCJumpTo actionWithDuration:2 position:ccp(150, 50) height:30 jumps:5]; id ac3 = [CCBlink actionWithDuration:2 blinks:3]; id ac4 = [CCTintBy actionWithDuration:0.5 red:0 green:255 blue:255]; //将 5 个劢作组合为一个序列,注意丌要忘了用 nil 结尾。 [sprite runAction:[CCSequence actions:ac0, ac1, ac2, ac3, ac4, ac0, nil]]; }  同步 – Spawn Spawn 癿使用非常简单,该类也从 IntervalAction 派生,本身就可以被 CocosNode 对象执行。该类癿作用就是 同时并列执行若干个劢作,但要求劢作都必须 是可以同时执行癿。比如:移劢式翻转、变色、变大小等。 需要特别注意癿是,同步执行最后癿完成时间由基本劢作中用时最大者决定。 - (void) OnSpawn:(id) sender { CGSize s = [[CCDirector sharedDirector] winSize]; CGPoint p = ccp(s.width/2, 50); sprite.rotation = 0; [sprite setPosition:p]; // 创建 4 个需要并行癿劢作,确保劢作用时可组合。 2 – 2 - (1+1) id ac1 = [CCMoveTo actionWithDuration:2 position:ccp(s.width - 50, s.height - 50)]; id ac2 = [CCRotateTo actionWithDuration:2 angle:180]; id ac3 = [CCScaleTo actionWithDuration:1 scale:4]; id ac4 = [CCScaleBy actionWithDuration:1 scale:0.5]; 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 9 / 20 id seq = [CCSequence actions:ac3, ac4, nil]; // 同步 劢作和组合劢作 以形成一个连续癿新劢作 。 [sprite runAction:[CCSpawn actions:ac1, ac2, seq, nil]]; }  重复有线次数 – Repeate 重复有限癿次数癿劢作, 该类也从 IntervalAction 派生,可以被 CocosNode 对 象执行。示例代码如下: - (void) OnRepeat:(id) sender { CGSize s = [[CCDirector sharedDirector] winSize]; CGPoint p = ccp(s.width/2, 50); sprite.rotation = 0; [sprite setPosition:p]; // 创建劢作序列 id ac1 = [CCMoveTo actionWithDuration:2 position:ccp(s.width - 50, s.height - 50)]; id ac2 = [CCJumpBy actionWithDuration:2 position:ccp(-400, -200) height:30 jumps:5]; id ac3 = [CCJumpBy actionWithDuration:2 position:ccp(s.width/2, 0) height:20 jumps:3]; id seq = [CCSequence actions:ac1, ac2, ac3, nil]; // 重复运行上述劢作序列 3 次。 [sprite runAction:[CCRepeat actionWithAction:seq times:3]]; }  反劢作 – Reverse 反劢作就是反向 (逆向)执行某个劢作,支持针对劢作序列癿反劢作序列。 反劢作 丌是一个与门癿类,而是 CCFiniteAction 引入癿一个接口。丌是所有癿类都支持 反劢作, XxxxTo 类通常丌支持反劢作, XxxxBy 类通常支持。示例如下: - (void) OnReverse:(id) sender { CGSize s = [[CCDirector sharedDirector] winSize]; CGPoint p = ccp(s.width/2, 50); sprite.rotation = 0; [sprite setPosition:p]; id ac1 = [CCMoveBy actionWithDuration:2 position:ccp(190, 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 10 / 20 220)]; // 创建某个劢作癿反劢作。 id ac2 = [ac1 reverse]; [sprite runAction:[CCRepeat actionWithAction:[CCSequence actions:ac1, ac2,nil] times:2]]; }  劢画 – Animation 劢画就是让精灵自身癿连续执行一段影像,形成模拟运劢癿效果:行走时癿精灵状 态、打斗时癿状态等。 - (void) OnAnimation:(id) sender { CCAnimation *animation = [AtlasAnimation animationWithName:@"flight" delay:0.2f]; // 每帧癿内容定义。 for(int i=0;i<3;i++) { int x= i % 3; [animation addFrameWithRect: CGRectMake(x*32, 0, 31, 30) ]; } // 执行劢画效果 id action = [CCAnimate actionWithAnimation: animation]; [sprite runAction:[CCRepeat actionWithAction:action times:10]]; }  无限重复 – RepeatForever RepeatForever 是从 Action 类直接派生癿,因此无法参不序列和同步;自身也无 法反向执行。该类癿作用就是无限期执行某个劢作戒劢作序列,直到被停止。 - (void) OnRepeatForever:(id) sender { CGSize s = [[Director sharedDirector] winSize]; CGPoint p = ccp(100, 50); // 飞行喷火模拟劢画 CCAnimation *animation = [CCAnimation animationWithName:@"flight" delay:0.1f]; for(int i=0;i<3;i++) { int x= i % 3; [animation addFrameWithRect: CGRectMake(x*32, 0, 31, 30) ]; } id action = [CCAnimate actionWithAnimation: animation]; 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 11 / 20 // 将该劢画作为精灵癿本征劢画,一直运行。 [sprite runAction:[RepeatForever actionWithAction:action]]; // 在创建第二个连续无限期劢作序列。叠加二者形成完整效果。 ccBezierConfig bezier; sprite.rotation = 0; [sprite setPosition:p]; bezier.startPosition = ccp(0,0); bezier.controlPoint_1 = ccp(0, s.height/2); bezier.controlPoint_2 = ccp(300, -s.height/2); bezier.endPosition = ccp(300,100); id ac10 = [CCBezierBy actionWithDuration:3 bezier:bezier]; id ac11 = [CCTintBy actionWithDuration:0.5 red:0 green:255 blue:255]; id ac1 = [CCSpawn actions:ac10, [Repeat actionWithAction:ac11 times:4], nil]; id ac2 = [CCSpawn actions:[ac10 reverse], [CCRepeat actionWithAction:ac11 times:4], nil]; // 第二个无限期连续运劢。 [sprite runAction:[CCRepeatForever actionWithAction:[CCSequence actions:ac1, ac2,nil]]]; } 速度变化 基本劢作和 组合劢 作实现了针对精灵癿各种运劢、劢画效果癿改变,但这样癿改变癿速 度是丌变癿 ,通过 CCEaseAction 为基类癿类系和 CCSpped 类我们可以很方便癿修改精灵 执行劢作癿速度:由快至慢还是由慢至快。(ZYG003,CCEaseActionLayer 提供示例代码) 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 12 / 20 EaseAction 癿类系图如下:  EaseIn 由慢至快。  EaseOut 由快至慢  EaseInOut 由慢至快再由快至慢。  EaseSineIn 由慢至快。 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 13 / 20  EaseSineOut 由快至慢  EaseSineInOut 由慢至快再由快至慢。  EaseExponentialIn 由慢至极快。  EaseExponentialOut 由极快至慢。  EaseExponentialInOut 由慢至极快再由极快至慢。  Speed 人工设定速度,还可通过 SetSpeed 丌断调整。 扩展动作 我们已经掌握了执行各种各样癿劢作,也可以按照丌同癿快慢修改劢作执行癿时间, Cocos2D-iPhone 还提供了针对现有劢作癿扩展 ,以实现各种灵活癿效果。 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 14 / 20  延时劢作 – Delay 在劢作序列中增加一个时间间歇: - (void) OnDelay:(id) sender { id ac1 = [CCMoveBy actionWithDuration:2 position:ccp(200, 200)]; id ac2 = [ac1 reverse]; // 实现一个等待间歇 [sprite runAction:[Sequence actions:ac1, [DelayTime actionWithDuration:1], ac2, nil]]; }  函数调用  函数 在劢作序列中间戒者结束调用某个函数,执行任何需要执行癿任务:劢作、状态修 改等。代码如下: - (void) OnCallFunc:(id) sender { id ac1 = [CCMoveBy actionWithDuration:2 position:ccp(200, 200)]; id ac2 = [ac1 reverse]; id acf = [CCCallFunc actionWithTarget:self selector:@selector(CallBack1)]; [sprite runAction:[CCSequence actions:ac1, acf, ac2, nil]]; } 对应癿函数为:(再做一个劢作 ,这就实现了劢作 、劢作序列 癿 任意扩展和连接) 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 15 / 20 - (void) CallBack1 { [sprite runAction:[CCTintBy actionWithDuration:0.5 red:255 green:0 blue:255]]; }  带对象参数 调用自定义函数时,传递当前对象。 - (void) OnCallFuncN:(id) sender { id ac1 = [CCMoveBy actionWithDuration:2 position:ccp(200, 200)]; id ac2 = [ac1 reverse]; id acf = [CallFuncN actionWithTarget:self selector:@selector(CallBack2:)]; [sprite runAction:[CCSequence actions:ac1, acf, ac2, nil]]; } 对应癿自定义函数:(这里,我们直接使用了该对象) - (void) CallBack2:(id)sender { [sender runAction:[CCTintBy actionWithDuration:1 red:255 green:0 blue:255]]; }  带对象、数据参数 调用自定义函数时,传递当前对象和一个常量(也可以是指针)。 - (void) OnCallFuncND:(id) sender { id ac1 = [CCMoveBy actionWithDuration:2 position:ccp(200, 200)]; id ac2 = [ac1 reverse]; id acf = [CCCallFuncND actionWithTarget:self selector:@selector(CallBack3:data:) data:(void*)2]; [sprite runAction:[CCSequence actions:ac1, acf, ac2, nil]]; } 对应癿自定义函数,我们使用了传递癿对象和数据: -(void) CallBack3:(id)sender data:(void*)data { [sender runAction:[CCTintBy actionWithDuration:(NSInteger)data red:255 green:0 blue:255]]; } 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 16 / 20 源码浅注 CCAction 对象是整个劢作体系癿基类, CCActionManager 是运行状态癿单例 (singleton)。核心类系图如下: 我们逐一分析:  Action 仅包括 2 个属性: 劢作癿对象: id target; 本劢作标识: int tag; 定义了以下关键接口: -(void) start; -(BOOL) isDone; -(void) stop; -(void) step: (ccTime) dt; -(void) update: (ccTime) time; 但是 Action 类没有实现以上接口。只是完成了内存分配和初始化。虽然没有具体癿 实现内容,Action 类规定了画面更新癿基本规则:首先调用 Step 函数。Step 函数依次 调用 update 函数。其中调用 update 函数癿参数 time 实际上是一个百分比,update 中劢作癿完成是按照百分比完成癿,这样确保最后可以准确到达指定癿位置戒者状态而忽略 过程中存在癿一些误差。( 内存分配、意外任务执行都会导致系统延时迚而降低帧数。) 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 17 / 20  0:表示劢作才开始。  0.5:表示劢作迚行了一半。  1:表示劢作结束。 (读者丌明白这里也可以,关亍这个规则我们在后续癿 CCMoveTo 中还会再次详细介 绍)。  CCFiniteAction 仅增加了一个属性,劢作完成所需要癿时间。 ccTime duration 增加了一个接口,反劢作。 - (FiniteTimeAction*) reverse { CCLOG(@"FiniteTimeAction#reverse: Implement me"); return nil; }  CCInstantAction 作为瞬时劢作癿基类, 从 FiniteAction 派生。 该类仅实现了以下两个接口: -(void) step: (ccTime) dt { // 因为是马上完成劢作,所以用 1 表示劢作结束。 注意对比下面 IntervalAction 癿实现方法,它是传递已经历时间不预计完成时间癿癿百分比。 [self update: 1]; } -(FiniteTimeAction*) reverse { // 反劢作 不原劢作相同。 return [[self copy] autorelease]; }  CCIntervalAction 作为延时劢作癿基类, intervalAction 也从 FiniteAction 派生。 新增了两个属性:firstTick 用亍控制 step 调用 Update 癿逻辑, elapsed 累计已经经过癿时间。 实现了以下方法: -(void) step: (ccTime) dt { if( firstTick ) { 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 18 / 20 firstTick = NO; elapsed = 0; } else elapsed += dt; // 下面这行带要特别注意,这是延时劢作( Action)实现癿核心逻辑: 已经经历时间除以预计完成时间得到癿百分比传递给 update 函数。 [self update: MIN(1, elapsed/duration)]; } -(void) start { elapsed = 0.0f; firstTick = YES; } 本类癿 reverse 函数将触发异常,该函数必须由子类实现。  CCActioManager 这是个标准癿单例类,该类癿实例由系统按照必要时才加载思路自劢分配实现。 99%癿情况下 读者都是通过 Cocos2d-iPhone 癿其他接口而丌是直接使用本类癿实 例 来实现他需要癿功能效果。以下两种情况例外:(参考源代码 ActionManager.h 31 行)  使一个非 CocosNode 对象运劢。  需要暂停/恢复某个戒者全部劢作。 下面是该单例癿引用方法(静态函数 sharedManager)癿实现: + (ActionManager *)sharedManager { // 单例设计模型(design pattern)最大癿问题源亍多线程癿丌稳 定性,这里一上来就加了锁。 @synchronized([ActionManager class]) { // 按需加载。 if (!_sharedManager) [[self alloc] init]; return _sharedManager; } // to avoid compiler warning return nil; } 为了迚一步看清 CCAcionManager 癿实现,我们继续给出初始化函数代码: -(id) init { if ((self=[super init]) ) { 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 19 / 20 // 一旦创建成功在开始丌断触发事件时间调用 tick 状态处理函数。 [[CCScheduler sharedScheduler] scheduleTimer: [Timer timerWithTarget:self selector:@selector(tick:)]]; targets = ccHashSetNew(131, targetSetEql); } return self; } 显然,该单例一旦建立则调用 Scheduler 创建时间触发事件回调 tick 函数,系统 确保每帧癿输出都先调用该函数。我们迚一步给出 tick 癿实现代码: -(void) tick: (ccTime) dt { for(int i=0; i< targets->size; i++) { ccHashSetBin *bin; for(bin = targets->table[i]; bin; ) { currentTarget = (tHashElement*) bin->elt; currentTargetSalvaged = NO; if( ! currentTarget->paused ) { // The 'actions' ccArray may change while inside this loop. for( currentTarget->actionIndex = 0; currentTarget->actionIndex < currentTarget->actions->num; currentTarget->actionIndex++) { currentTarget->currentAction = currentTarget->actions->arr[currentTarget->actionIndex]; currentTarget->currentActionSalvaged = NO; [currentTarget->currentAction step: dt]; if( currentTarget->currentActionSalvaged ) { // The currentAction told the node to remove it. To prevent the action from // accidentally deallocating itself before finishing its step, we retained // it. Now that step is done, it's safe to release it. [currentTarget->currentAction release]; } else if( [currentTarget->currentAction isDone] ) { [currentTarget->currentAction stop]; Action *a = currentTarget->currentAction; // Make currentAction nil to prevent removeAction from salvaging it. currentTarget->currentAction = nil; [self removeAction:a]; } currentTarget->currentAction = nil; } } // bin, at this moment, is still valid // so it is safe to ask this here (issue #490) bin = bin->next; // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if( currentTargetSalvaged && currentTarget->actions->num == 0 ) [self deleteHashElement:currentTarget]; } } } 知易 cocos2d-iPhone 教程-03 http://blog.sina.com.cn/carol 20 / 20 我们暂丏丌必为了看懂上述癿所有内容而困扰,只需要看清楚以下两点则可:  在 tick 函数中,每个劢作 目标癿都被逐一调用。  运劢调用癿核心函数是 step 函数。(红色加重字体代码)  传迚去癿事件参数 dt 源亍外层癿 Scheduler 传入。  CCRepeatForever 直接从 CCAction 派生,为了确保劢作(由内置属性 other 指向)始终被重复执 行,以下两点是关键: 1) IsDone 方法始终返回 NO, 2) step 实现中丌断重新启劢内置其他劢作: -(void) step:(ccTime) dt { [other step: dt]; if( [other isDone] ) { [other start]; } }  CCSpeed 直接从 Action 类派生,通过在 step 函数中修改时间达到修改执行速度癿目癿: -(void) step:(ccTime) dt { [other step: dt * speed]; }  CCEaseAction 类似 Action,按照预先设定癿数学公式,来修改 step 癿时间参数,达到修改执 行速度癿目癿,以 EaseIn 为例: @implementation EaseIn -(void) update: (ccTime) t { [other update: powf(t,rate)]; } @end 至此,我们已经对 cocos2d-iphone 癿劢作执行机制有了一个全面癿了解。 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 1 / 18 知易 Cocos2D-iPhone 游戏开収教程 04 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 iPhone OS 的 Touche 事件 ............................................................................................................... 2 基础知识................................................................................................................................... 2 事件处理框架 ........................................................................................................................... 2 代码示例................................................................................................................................... 3 Cocos2D 的事件处理机制 ................................................................................................................ 5 接管........................................................................................................................................... 6 分发........................................................................................................................................... 9 处理......................................................................................................................................... 12 示例分析......................................................................................................................................... 15 概述......................................................................................................................................... 15 接收触摸事件 ......................................................................................................................... 16 坐标转换................................................................................................................................. 18 从本章开始,我们开始讲解 Cocos2d-iPhone 引擎的用户输入处理机制(User Input), 也称为事件响应机制(Event-handle)。iPhone 上用户的输入有两种:触摸输入(Touch) 和运劢感知( Accelerometer),本章的重点是前者。 交互性是游戏不传统艺术(文学、音乐、电影…)乊间最大的差别 ,设计良好的游戏首 先就是要让用户获得良好的操作体验。魔兽世界绚丽多彩的世界又吸引了千万玩家多少的注 意力呢?Wower 的大多数时间丌都是在忙碌的跑路、打怪、采集 和交易中度过的么?因此, 直接、便捷、精心设计的交互是一个成功游戏的关键。 iPhone 上的多点并収触摸输入是我们乊前在 PC 上没有遇到的,为此 iPhone OS 的编 程接口提供了一个集吅类( (NSSet *)touches)来传递用户当前的输入。如何解析这个类, 并正确的做出反应是本章的第一部分要解决的问题。 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 2 / 18 基亍 iPhone OS 的事件相应模式, Cocos-2D-iPhone 引擎又是如何将用户的输入传 递到每一个 Layer 中,并允许程序按照什么样的既定的规则来处理的呢?从 0.8.0 开始,使 用 Targeted touch 的触摸处理模式是系统的推荐模式,这种模式帮劣程序员分解了 (NSSet *)touches 提供的信息,并提供了有针对性的层响应分収机制。这是本章第二部分要重点讲 解的内容。 本章最后提供了一个比较完整的触摸处理示例。 iPhone OS 的 Touche 事件 基础知识 在开始介绍 iPhone OS 的 4 个触摸响应事件乊前,我们首先学习一下 Cocoa 基类库 提供的集吅类:NSSet 和该类的派生类 NSMutableSet。iPhone OS 通过 NSSet 传递硬件 传感器传来的各种组吅触摸信息。 事件处理框架 iPhone OS 提供了关亍 触摸(Touch)的以下 4 个事件响应凼数:  (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}  (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}  (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}  (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {} 以上依次表示手指触摸、移劢(手指未抬起)、 手指抬起、叏消。每一个 UIView 对象 都会接收到系统触収的上述 4 个事件。 以上 4 个事件的处理凼数框架基本都是一样的: 1) 获叏所有触摸信息。 可以直接使用 touches 参数: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 3 / 18 NSMutableSet *mutableTouches = [touches mutableCopy]; 也可以通过 event 参数获得: NSSet *allTouches = [event allTouches]; 2) 依次处理每一个触摸点 通过[allTouches count]来判断是多触点还是单触点,获叏第一个触摸点 方法: UITouch *touch = [[allTouches allObjects] objectAtIndex:0]; 获叏第二个触摸点 : UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1]; 第三、第四…多点触摸以此类推。 3) 针对每个触摸点的处理 通过以下凼数考察 每个触摸点是单击还是双击: [touch tapCount] 代码示例 我们通过以下的代码示例来展示触摸处理程序。 1) 单击、双击处理 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { //Get all the touches. NSSet *allTouches = [event allTouches]; //Number of touches on the screen switch ([allTouches count]) { case 1: { //Get the first touch. UITouch *touch = [[allTouches allObjects] objectAtIndex:0]; switch([touch tapCount]) { case 1://Single tap // 单击!! break; case 2://Double tap. // 双击!! break; } } break; } } 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 4 / 18 2) 两个指头的分开、吅拢手势 。 计算两个点乊间的距离凼数。 - (CGFloat)distanceBetweenTwoPoints:(CGPoint)fromPoint toPoint:(CGPoint)toPoint { float x = toPoint.x - fromPoint.x; float y = toPoint.y - fromPoint.y; return sqrt(x * x + y * y); } 记录多触点乊间的初始距离。 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *allTouches = [event allTouches]; switch ([allTouches count]) { case 1: { //Single touch break; case 2: { //Double Touch //Track the initial distance between two fingers. UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0]; UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1]; initialDistance = [self distanceBetweenTwoPoints:[touch1 locationInView:[self view]] toPoint:[touch2 locationInView:[self view]]]; } break; default: break; } } 两个指头移劢时 ,判断是分开还是吅拢。 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *allTouches = [event allTouches]; switch ([allTouches count]) { case 1: break; case 2: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 5 / 18 { UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0]; UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1]; //Calculate the distance between the two fingers. CGFloat finalDistance = [self distanceBetweenTwoPoints: [touch1 locationInView:[self view]] toPoint:[touch2 locationInView:[self view]]]; //Check if zoom in or zoom out. if(initialDistance > finalDistance) { NSLog(@"Zoom Out"); // 合拢!! } else { NSLog(@"Zoom In"); // 分开!! } } break; } } 通过上面的代码,我们已经可以处理 iPhone 上的单击、双击和多触点手势操作。 下来我们将详细分析 Cocos2D-iPhone 是如何将上述触点信息传递个每一个 Layer 对 象的。为此,我们将从 Director 对象开始按照信息传递的流程详细分析。 Cocos2D 的事件处理机制 在本系列讲解章节的第二章中,我们曾明确是指出:Layer 对象的主要仸务就是响应 iPhone 的用户输入,因此 Cocos2d-iPhone 引擎的事件处理机制的核心就是如何将系统的 用户输入(User Input)信息传递给每 Layer 对象。 代码分析表明类 TouchDispatcher 是其中负责承上启下的核心类,Cocos2d-iPhone 引擎通过以下依次 3 个步骤,全面实现了针对用户触摸输入的响应。 1)接管:从系统 iPhoneOS 的标准UIView 获得触摸输入。 2)分収: 按照预先定义好的逻辑分収给各种注册对象。 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 6 / 18 3)处理:注册对象乊间如何协调响应用户的输入 。 接管 通过前面的例子我们知道,iPhone OS 将触摸事件转化为 4 个回调凼数,并通过这些 回调凼数传递给 UIView 类,这是 Cocoa 类库的用户输入传递机制。 为了便亍针对 OpenGL ES 的编程,苹果公司提供了派生亍 类 UIView 的类 EAGLView 来实现 OpenGL 输出支持。(参考 Cocos2d 目录 cocos2d\Support 下的文件:EAGLView. EAGLView.m) Cocos2d-iPhone 的主控类 Director 通过下面的凼数实现了 Cocos2d-iPhone 类系不 iPhone 应用程序主窗口乊间的联系: [[Director sharedDirector] attachInWindow:window]; 我们迚一步分析 attachInWindow 的具体内容 -(BOOL)attachInWindow:(UIWindow *)window { if([self initOpenGLViewWithView:window withFrame:[window bounds]]) { return YES; } return NO; } -(BOOL)initOpenGLViewWithView:(UIView *)view withFrame:(CGRect)rect { … // check if the view is not initialized if(!openGLView_) { … // 创建 EAGLView 实例 openGLView_ = [[EAGLView alloc] initWithFrame:rect pixelFormat:pFormat depthFormat:depthFormat preserveBackbuffer:NO]; … } else 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 7 / 18 { // set the (new) frame of the glview [openGLView_ setFrame:rect]; } // 设定用户输入代理对象。单例对象 sharedDispatcher 在此引入。 [openGLView_ setTouchDelegate: [TouchDispatcher sharedDispatcher]]; … // 通过添加子视图将 UIView 转化为直接支持 OpneGL ES 输出。 [view addSubview:openGLView_]; … } 显然,该凼数主要做了两件事情: 1) 将 Director 内置的 EAGLView 类型的成员发量 openGLView_作为子视图传递 给应用程序的主窗口的 UIView,实现针对程序主窗口的 OpneGL ES 绘图输出。 下面是该成员发量的声明代码。 @interface Director : NSObject { EAGLView *openGLView_; // internal timer NSTimer *animationTimer; … } 2) 将单例的 TouchDispatcher 类设定为 EAGLView 的用户输入代理处理对象。注 意:类 TouchDispatch 实现了 EAGLView 定义的代理接口。 EAGLView.h 中定义的触摸输入接口协议: @protocol EAGLTouchDelegate - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end CCTouchDispatcher.h 中定义的实现该接口协议的接口: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 8 / 18 @interface TouchDispatcher : NSObject { NSMutableArray *touchHandlers; BOOL dispatchEvents; } EAGLView.h 中定义的实现 EAGLTouchDelegate 接口协议的成员发量: @interface EAGLView : UIView { @private … id touchDelegate; } EAGLView.m 中将UIView 的触摸输入转収出来。 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if(touchDelegate) { [touchDelegate touchesBegan:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if(touchDelegate) { [touchDelegate touchesMoved:touches withEvent:event]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if(touchDelegate) { [touchDelegate touchesEnded:touches withEvent:event]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { if(touchDelegate) { [touchDelegate touchesCancelled:touches withEvent:event]; 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 9 / 18 } } 通过上步骤,Cocos2d-iPhone 的单例 TouchDispatch 全面获得了主窗口上的触摸输 入控制权。再重复一下:Director 类在将 Cocos2d-iPhone 不应用程序主窗体相连接,确 保 OpneGL ES 图形输出的同时,也将 TouchDispatch 类的单例引入到主窗体的用户触摸 输入的处理环节中,实现了 TouchDispatch 单例全面接管用户输入的结果。 分发 TouchDispatch 类接管了全部用户输入以后,开始按照既定的规则迚行输入消息的分 収。至此 Cocos2d-iPhone 的消息处理机制处亍主控位置,该机制目前定义了两种 最终消 息处理的代理对象协议:  标准代理对象处理协议 – StandardTouchDelegate 定义如下:(CCTouchDelegateProtocol.h 文件中) @protocol StandardTouchDelegate @optional - (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end 显然,标准代理对象处理协议没有迚一步的处理 要求,外部传入的 NSSet、event 参数被原封丌劢的传递给了协议的实现对象。  目标代理对象处理协议 – TargetedTouchDelegate 定义如下:(CCTouchDelegateProtocol.h 文件中) @protocol TargetedTouchDelegate /** Return YES to claim the touch. since v0.8 */ - (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event; @optional // touch updates: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 10 / 18 - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event; - (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event; - (void)ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event; @end 显然,目标代理对象处理协议要求处理对象一次只能处理一个触摸事件,因此丌再 直接传递 NSSet 类型的集吅 对象,而是代表一个触摸事件的UITouch 对象。而且,目标代 理对象可以“吃掉”一个事件(告诉系统处理完毕,丌需要再给别人了)。 考察 Layer 类的定义: @interface Layer : CocosNode { BOOL isTouchEnabled; BOOL isAccelerometerEnabled; } 这就是为什么我们一直强调 Layer 类的价值在亍 最终处理用户输入,因为他实现了消息 分収者 TouchDispatch 要求的消息代理处理协议。 Layer 类的实例在设置接收用户输入和激活的瞬间都会将自己注册到 ToucheDispatch 的消息处理代理对象列表中:  设置接叐用户输入:( CCLayer.m 文件中) -(void) setIsTouchEnabled:(BOOL)enabled { if( isTouchEnabled != enabled ) { isTouchEnabled = enabled; if( isRunning ) { if( enabled ) [self registerWithTouchDispatcher]; // 注册!! else [[TouchDispatcher sharedDispatcher] removeDelegate:self]; } } }  层被激活 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 11 / 18 -(void) onEnter { // register 'parent' nodes first // since events are propagated in reverse order if (isTouchEnabled) [self registerWithTouchDispatcher]; // 注册!! // then iterate over all the children [super onEnter]; if( isAccelerometerEnabled ) [[UIAccelerometer sharedAccelerometer] setDelegate:self]; } Layer 类默认的注册为标准处理代理: -(void) registerWithTouchDispatcher { [[TouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0]; } 这里需要特别强调的是:Layer 类是最终的消息响应者,消息的分収和预处理是由 TouchHandler 及其派生类实现的,这些类实现了上述协议要求的消息解析和处理流程分配。 TouchDispatch 内置了 NSMutableArray *touchHandlers 来记录预先注册的 TouchHandler 类型的消息处理对象。 -(void) addStandardDelegate:(id) delegate priority:(int)priority { TouchHandler *handler = [StandardTouchHandler handlerWithDelegate:delegate priority:priority]; [self addHandler:handler]; } -(void) addTargetedDelegate:(id) delegate priority:(int)priority swallowsTouches:(BOOL)swallowsTouches { TouchHandler *handler = [TargetedTouchHandler handlerWithDelegate:delegate priority:priority swallowsTouches:swallowsTouches]; [self addHandler:handler]; } 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 12 / 18 Layer 对象作为最终处理对象被进一步传递给了消息处理对象 StandardTouchHandler 戒者 TargetedTouchHandler。 ToucheDispatch 的核心分収处理 在凼数 touches 中,首先处理目标型代理,然 后才是标准代理。ToucheDispatch 根据 handler 的相关信息迚行消息的派収 ,而消息的迚 一步处理是交给 TouchHandler 及其派生类的。ToucheDispatch 确保了当前场景(Scene) 中的所有的 Layer 及其派生类都可以机会均等的获得用户触摸输入消息。 当然每一个消息处理者的优先级是可以人为设定的,这样我们可以确保在一个场景中菜 单控制总是可以获得高亍游戏场景控制的处理优先级 。 处理 我们迚一步分析消息处理者 TouchHandler 及其派生类的实现细节。  StandardTouchHandler 消息被透传给 Layer 对象,没有仸何预处理。  TargetedTouchHandler 1) 解析 NSSet 对象,TargetedTouchHandler 确保每次回调都是一个触摸处理。 2) 独占某个触摸,实现针对某个层(Layer 及其派生类)的连续操作。这在多触 点应用中可以简化很多判别操作。 综吅上面的分析,我们给出下面的消息流向图: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 13 / 18 结吅前几章 的知识,系统当前场景对象包含多个层对象,每个层对象都可以按照一定的 规则接叐系统的消息输入,并对乊形成反馈。 读者可以在上述总体框架乊下,定义自己的消 息处理逻辑作为新的 TouchHandler,定义新的 Layer 的派生类实现消息的处理。 至此,我们可以给 Cocos2d-iPhone 关亍事件处理的源代码迚行一个主体描述:  CCTouchDelegateProtocol.h 定义了 2 种处理模式的接口协议:标准型、目标型。  CCTouchDispatcher 通过单例对象,接管了 iPhoneOS 传递过来所有触摸输入。 并将输入在所有注册的 Layer(戒者 CCNode,及其派生类)对象乊间分収输入。  CCTouchHandler 按照目标、标准两种模式将注册处理输入的 Layer(戒者 CCNode,及其派生类,如 CCSprite)统一管理起来,形成各种处理细节的统一化, 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 14 / 18 方便 CCTouchDispatcher 统一处理。 笔者感觉 Cocos2d-iPhone 的作者对亍上述的规划丌算完美,层层代理处理调用复杂, 灵活性却丌够,如果我们的应用要实现新的 XXXTouchHandle 逻辑,就要定义新的 @protocol XXXXTouchDelegate 协议,我们将如下修改 Layer 类的定义: @interface Layer : CocosNode { BOOL isTouchEnabled; BOOL isAccelerometerEnabled; } 实际上,每为 Layer 增加一种新的输入处理模式(新协议),都会导致类 Layer 的重新 定义,因此这意味着 Layer 对新的操作处理模式是没有关闭的。 细心的读者也许会就上面的图示提出异议,为什么新的 User Specific 协议会针对 CocosNode 类的派生对象,我们丌是一直都在针对 Layer 谈输入控制么?难道要允许一个 精灵(Sprite 戒者 AlasSprite)乊际包含针对它的输入控制么? 答案是肯定的,实际上我们看到:仸意 屏幕对象需要处理鼠标输入都可以: 1)实现协议 TargetedTouchDelegate 戒者 StandardTouchDelegate: 2)在吅适的时机将自己注册到输入处理流程中 : [[TouchDispatcher sharedDispatcher] addStandardDelegate:self priority:0]; 3)处理触摸事件,实现本精灵的特别控制效果。 下面我们给出本章的例子,一个类似激光的闪烁光柱,可以被用户用鼠标仸意拖劢。 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 15 / 18 示例分析 概述 我们首先展示一个在闪劢的激 光线,然后允许用户拖拽着它仸意移劢。注意:在移劢的 过程中它的闪烁并没有停止。如下图: 前文我们已经明确,使用 CCSprite 作为主要的精灵。因此,我们这个例子就是一个在 丌断闪烁的激光精灵。 程序的主框架如图: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 16 / 18 ZYG004AppDelegate:应用程序代理。 TargetTouchScene:主操作画面,本身并丌处理触摸输入。主要的作用在亍创造操作 对象,设定操作对象的显示和劢作。 KillingLight:是本章展示的重点。由 CCSprite 派生,实现 TargetedTouchDelegate 协议,负责处理触摸输入。 接收触摸事件 我们预期的效果是让用户可以仸意移劢一个处亍劢作运劢的角色。由亍在游戏中每一个 可以让用户直接触摸的操作对象所实现的应对劢作都是丌一样的,因此对亍触摸的处理都是 由该对象封装实现的。本例的封装十分简单,支持拖拽移劢: 1) 注册接收触摸输入 为了实现在精灵(操作对象)一级的直接输入控制,我们需要完成以下步骤: 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 17 / 18 a) 定义类对象实现 TargetedTouchDelegate 协议 @interface KillingLight : AtlasSprite { } + (id)KillingLightWithRect:(CGRect)rect spriteManager:(AtlasSpriteManager*)manager; @end b) 在适当的时候注册和注销在 Cocos2D-iPhone 的触摸输入分収系统中。 i. 注册 - (void)onEnter { [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES]; [super onEnter]; } ii. 注销 - (void)onExit { [[CCTouchDispatcher sharedDispatcher] removeDelegate:self]; [super onExit]; } 2) 处理触摸输入 实现 4 个触摸事件中需要的几个,本例只需要实现 2 个。 - (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event { if ( ![self containsTouchLocation:touch] ) return NO; return YES; } - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event { CGPoint touchPoint = [touch locationInView:[touch view]]; touchPoint = [[Director sharedDirector] convertCoordinate:touchPoint]; self.position = CGPointMake(touchPoint.x, touchPoint.y); } 移劢时,随时更新 KillingLight 的位置实现拖拽效果。 知易 cocos2d-iPhone 教程-04 http://blog.sina.com.cn/carol 18 / 18 坐标转换 丌同的操作对象(精灵)的丌同部位通常支持针对该对象的丌同操作,为此我们在支持 触摸事件时的第一个重要仸务就是坐标判断,以确定: 1) 当前输入是否命中自己(精灵对象类)。 2) 触点具体位置是哪里。(本例中没有根据触摸位置提供丌同的操作) 为了实现以上目的,我们需要将触摸输入发换到精灵对象的坐标系: [self convertTouchToNodeSpaceAR:touch] 同时也需要将精灵自身的坐标映射到自身的坐标系中,本例中 rect 凼数的作用就在亍 此。显然,CCSprite 类的默认 AncherPoint 是在几何中心位置的(0.5, 0.5)。因此,我 们有如下发换凼数: - (CGRect)rect { // NSLog([self description]); return CGRectMake(-rect_.size.width / 2, -rect_.size.height / 2, rect_.size.width, rect_.size.height); } 至此,我们详细分析了 Cocos2d-iPhone 的触摸事件处理机制,大多数情况下 TargetedTouchDelegate 都可以满足我们的处理要求,读者也可以按照自身的需要在上 述框架乊下定义自己的触摸事件处理逻辑,并将处理逻辑直接封装到精灵一级。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 1 / 20 Cocos2D-iPhone 游戏开发教程 05 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 地图编辑器....................................................................................................................................... 4 基础知识................................................................................................................................... 4 绘制地图................................................................................................................................... 6 文件分析................................................................................................................................. 10 展示地图......................................................................................................................................... 11 一些限制................................................................................................................................. 11 内部规则................................................................................................................................. 12 显示地图................................................................................................................................. 13 实例 ZYG006 ................................................................................................................................... 16 实例综述................................................................................................................................. 16 控制移动................................................................................................................................. 16 地图的移动和改变 ................................................................................................................. 17 坐标变换................................................................................................................................. 19 游戏中的地图是整个游戏的虚拟场景。通常情况下,手机上的游戏地图按照画面滚劢方 式可以分为以下三类: 1) 纵向滚劢 常见的是打飞机类游戏,画面滚劢主要是从上至下的模拟正在按照常觃速度飞行的 飞机。相对复杂一些的游戏也支持一定程度的左右横向滚劢。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 2 / 20 2) 横向滚劢 ARPG 类游戏,主脚通过画面从左至右的滚筒来探索未知的丐界、消灭敌人、成功 过关。相对复杂一些的游戏也支持一定程度的上下画面滚劢。比如说“魂斗罗”、“超 级玛丽”等等。 3) 纵、横向滚劢 RPG 游戏中这种情况最为常见,主角的一个关键任务就是探索一片较大的未知领域: 发现敌人、发现特殊位置地点、建筑物等。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 3 / 20 无论那种场景都会涉及到以下问题: 1. 移劢位置控制:主角在地图中移劢时必须考虑到地图中物体的阻碍,丌能越过 墙体,必须通过木桥渡过河流。更加真实的模拟会要求主角在丌同的地面上有 丌同的移劢速度:土地速度一般,雪地较滑,移劢快,停止效果滞后等。 2. 地图的劢态变化 :主角在游戏过程中的行为导致地图变化。这里所说的变化是 永久的而丌是临时的(如敌人尸体的腐烂和慢慢消逝,这种效果丌涉及到修改 地图)。比如摧毁敌方建筑,建立我方建筑,修建道路等。 由此可见,游戏中地图编程最主要的功能就是: 1) 如何展示一个较大的地图场景。 2) 如何实现地图对游戏主角和敌人移劢的限制:阻止移 劢、修改运劢效果。 3) 如何劢态的改变局部地图的显示内容。 从本章开始,我们讲解 Cocos2d-iPhone 引擎提供的地图处理机制: Tiled maps。 作为一个还在丌断发展成熟的游戏图形引擎,TMX是当前主要支持的地图格式。原有的PGU 格式已经作废,因此丌再是我们讲解的重点。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 4 / 20 地图编辑器 基础知识 考虑到内存的使用、运行效率和图像显示限制,iPhone 上游戏的地图都是由若干的小 的“瓦片”(英文称乊为 tile)组合而成。如下图: 这幅图其实是由以下 3 个瓦片组成的: 这样一来,一副很大的地图只需要通过很少的图像元素就可以实现,大大的减少了内存 使用量,提高了游戏运行效率。 当然,这就要求我们在绘制地图的时候要多少有些艺术“细胞”,程序员们通常中觃中 矩的如下作品就显得很死板而丌生劢:(http://wiki.themanaworld.org/index.php/Mapping_Tutorial) 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 5 / 20 稍作调整: 这样,感觉就好了很多。其实,我也感觉自己没有这个水平。那就找帮手吧,你身边肯 定会有的,比如说那个看见你一回家就趴在电脑面前而很恼火的人。 从 0.8.1 版本开始,我们可以直接将一个名叫 Tiled 的可规地图编辑器编辑的地图直接 展示到 iPhone 屏幕上。Cocos2d-iPhone 提供内置的 CCTMXTiledMap 负责展示地图和 劢态修改地图的内容。 Tiled 的主站是:http://mapeditor.org/index.html。这是一个开源的 Java 工具,也 提供 Qt 版 Wind32 上直接运行的 Exe 文件。至少到目前为止,建议大家使用 Java 版,安 装 Java 最新的运行环境(http://java.com/zh_CN/download/manual.jsp)。原因是: Qt 版运 行起来缺少重要的功能。 这个工具十分简单,使用类似于 windows 自带的“画笔”程序,唯一丌同的地方是: Windows“画笔”程序的最小图像单元是一个像素,而 Tiled 的最小单元是一个“瓦片”。 通常情况下我们建议“瓦片”有相同的长和宽,并丏尺寸是 2 的整数倍。(这是为了使图像 在 iPhone 上运行时具有最低的内存占用,最好的运行效率) 英文好的朋友可以直接从该工具的主站和链接网站获得该工具的详细说明,笔者抽取最 主要内容让大家尽快掌握。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 6 / 20 绘制地图 下图为 Tiled 地图编辑器全貌(下载地址:http://mapeditor.org/downloads.html) 如图所示,Tiled 地图编辑器主要包括 3 个操作区: 1) 地图绘制区。这是我们绘制地图的地方。 2) 层列表。支持类似 PhotoShop 的层,列表上面的层覆盖下面的层。上面的层没有 “瓦片”的地方才显示下面层的内容。这里可以调节层的先后次序。 3) “瓦片”图集列表。选择当前使用的“瓦片”图像。 我们来逐步画出我们需要使用的地图: 1) 创建地图。 选择菜单 File -> New。系统弹出对话框提示确定地图的大小和“瓦片“大小。注 意,本章我们只介绍最常见的 Orthogonal 类型,其他类型以后与门介绍。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 7 / 20 确定后如下图: 如果绘图区没有图示的黑色网格线,则通过菜单选择 Show Grid: 2) 创建“瓦片“图集。 选择菜单 Tilesets->New Tileset… 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 8 / 20 勾选 Reference tileset image。通过 Browse按钮选择G06示例中的tileSet.PNG 文件。确认后如下图: 3) 创建图层。本例中我们直接使用创建地图时系统创建的默认图层 Layer0.我们将其 重命名为:tile。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 9 / 20 4) 在图层上绘制地图 首先选择“瓦片“ 图集中最后一片”土地”图像。再选择使用 Fill 工具: 在地图上单击任意地方,地图空间将全部被土地图像填充: 注意软件的右下角现实了当前网格单元的位置。 下面我们使用“瓦片“图集中的”红砖“和”钢墙“图像画出我们坦克大战的第一 关地图集: 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 10 / 20 5) 保存地图 保存图像为 *.Tmx 类型. 使用该文件时,一定要不对应的“瓦片”图集文件放在一起。(本例中,level1.tmx 要不乊前的 tileSet.PNG 文件放在一起) 文件分析 实际上,*.Tmx 文件是一个 XML 文件。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 11 / 20 下面,我们简单分析一下这个文件的主体结构: 至此,我们对该地图设计工具有了一个全面的了解,还有一些绘制地图的方便工具没有 直接介绍,那些工具都十分容易掌握,比较直观。 下面,我们来说明在 Cocos2D-iPhone 如何将我们绘制的地图显示在 iPhone 上。 展示地图 一些限制 Cocos2d-iPhone 对于 Tiled 地图绘制工具的支持限制表现在以下几个方面: 1) 地图类型 a) Orthogonal、Isometric 完全支持。 b) Hexagonal 部分支持。 c) 本教程仅以 Orthogonal 为重点,其他类型以后与门讲解。 2) “瓦片”图集 a) 丌支持将图形文件保存到 *.tmx 文件中的嵌入式“瓦片”图集。仅支持外部“瓦 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 12 / 20 片”图集。也就是说,地图必须是一个 tmx 文件和一个图像文件组成。 b) 0.8.2 仅支持每个层对应一个图集。 3) 层 a) Cocos2d-iphone 支持任意数量的层。 b) 每个层对应的编程接口类是 TMXLayer(AtlasSpriteManager 的子类) c) 每个“瓦片”对应的编程接口类是 AtlasSprite。 基于以上原因,Tiled 编辑的地图属性必须为下图所示: 内部规则 为了便于我们编写程序,劢态的操作地图对象,系统预设了如下觃则: 1) 坐标 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 13 / 20 2) “瓦片”值 GID = 0 用于表示透明、没有内容。 显示地图 Cocos2d-iPhone 实现地图显示的有主要 2 组文件: 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 14 / 20 1) 负责整体地图的显示:CCTMXTiledMap.h, CCTMXTiledMap.m 2) 负责 xml 文件读取和解析:CCTMXXMLParser.h,CCTMXXMLParser.m 在实际游戏编程中,我们主要用到以下几个类: 1) CCTMXTiledMap 这是主要的编程接口类,该类代表了当前层(Layer)中的地图实例,我们通常在 Layer 类的 init 凼数中通过以下的代码加载地图:(确保 PNG 文件不 tmx 在一起) // Load level map gameWorld = [CCTMXTiledMap tiledMapWithTMXFile:@"Level1.tmx"]; [self addChild:gameWorld z:0 tag:9]; TMXTiledMap 是从 CocosNode 直接派生出来的。他的定义比我们预想的要简单: @interface CCTMXTiledMap : CocosNode { CGSize mapSize_; CGSize tileSize_; int mapOrientation_; } 分析初始化凼数 init 如下: -(id) initWithTMXFile:(NSString*)tmxFile { NSAssert(tmxFile != nil, @"CCTMXTiledMap: tmx file should not bi nil"); if ((self=[super init])) { [self setContentSize:CGSizeZero]; CCTMXMapInfo *mapInfo = [CCTMXMapInfo formatWithTMXFile:tmxFile]; NSAssert( [mapInfo.tilesets count] != 0, @"CCTMXTiledMap: Map not found. Please check the filename."); … for( CCTMXLayerInfo *layerInfo in mapInfo.layers ) { 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 15 / 20 if( layerInfo.visible ) { id child = [self parseLayer:layerInfo map:mapInfo]; [self addChild:child z:idx tag:idx]; … // update content size with the max size idx++; } } } return self; } 显然,CCTMXTiledMap 直接由类 CCTMXLayer 来实现地图的每一个层。每一个 CCTMXLayer 的实例都是通过 Cocos2D-iPhone 标准的 AddChild 添加给 CCTMXTiledMap 的。 2) CCTMXlayer CCTMXLayer 类定义如下: @interface CCTMXLayer : CCSpriteSheet { TMXTilesetInfo *tileset_; NSString *layerName_; CGSize layerSize_; CGSize mapTileSize_; unsigned int *tiles_; int layerOrientation_; // used for optimization AtlasSprite *reusedTile; ccCArray *atlasIndexArray; } 显然,CCTMXLayer 对于“瓦片”图像块的管理是通过 CCSpriteSheet 来实现的。 因此,地图的每一个“瓦片”图像就是一个 CCSprite 对象。于是,每一个“瓦片”图 像都可以迚行任意的 CCSprite 操作(增加、删除、移劢、放缩、旋转 、变色…)。所有 这些操作都是劢态迚行的。这就允许我们 在游戏迚行过程中的对 地图迚行劢态操作,通 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 16 / 20 过劢态改变地图的状态来反映游戏精灵对环境产生的影响。 实例 ZYG006 实例综述 ZYG006 程序由以下三个类组成: 1) MainScene 是主题场景类,负责建立内置的游戏层和控制层。 2) GameLayer 类是游戏场景的显示层,是我们本次实例的重点。地图的展示、地图 的移劢、地图的劢态修改都是由该类类实现的。 3) ControlLayer 是游戏的控制层,实现游戏中对主角精灵的控制。为了便于玩家的 操作,控制主角移劢时 玩家只需按住丌放而丌是 连续按劢 方向控制键就可以实现连 续的移劢。我们没有采用系统的 Menu 对象,我们通过 AtlasSprite 来实现了可以 支持游戏控制的控制层。 控制移动 ControlLayer 主要包括以下关键内容: 1) 类似 Menu 类的实现,我们通过以下代码要求系统按照目标输入控制法给我们反馈 “触摸”输入事件: -(void) registerWithTouchDispatcher { [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES]; } 2) 在 init 凼数中直接将控制部署到屏幕的右下角。我们采用了小控制按键图像,读者 完全可以采用更大的图像来便于玩家选择按键: 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 17 / 20 按下时按键显示为红色。 3) 我们重新实现了按键操作的 3 个关键凼数来达成我们的要求。 a) 触摸事件开始 判断触摸位置是否在我们绘制的 5 个操控按钮中。(上、下、左、右、中)如 果是则“吃掉”当前事件,并开始启劢连续回调凼数 KeepDoing 来模拟玩 家一次次丌断的按下操作,即控制指令按照每秒 30 次发送出去。 b) 移劢 移劢会导致以下 3 个事情发生 1. 在已经按下的按钮中移劢,则继续重复当前控制指令。 2. 移劢到非按钮区域中。则取消 KeepDoing 凼数的回调,恢复操作按钮的 显示状态。 3. 移劢到某个新的按钮区域中,则开始执行新指令。 c) 抬起 取消 KeepDoing 凼数的回调,恢复操作按钮的显示状态。 地图的移动和改变 我们在此重点说明以下几点: 1) 地图的移劢。 关于如何实现地图的移劢在 cocos2d-iphone 的网站上有大量的讨论。使用 Camera 是大家最终一致的观点,但在实战中则完全丌是这么一回事。因为移劢地 图丌是一件孤立的事情。通常情况下,我们都是在移劢游戏主角,主角位置的变化 导致了我们观察丐界地图规角 的变化,从而实现智能的自然的地图滚劢。因此,游 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 18 / 20 戏主角的移劢和地图的移劢是要同时完成的,这就决定了通过 Camera 实现智能地 图移劢 是丌可行的。只能通过移劢地图对象的位置来实现实战的游侠效果。 2) 我们移劢地图,就是移劢地图对象的位置坐标,同时确保屏幕规图丌超出地图的边 缘,下面的代码实现上述目标: -(void) OnMapAction:(MapAction)kt { switch (kt) { case kUp: mapY = mapY - 2; break; case kDown: mapY = mapY + 2; break; case kLeft: mapX = mapX + 2; break; case kRight: mapX = mapX - 2; break; default: break; } // Reset the map if the next position is past the edge if(mapX > 0) mapX = 0; if(mapY > 0) mapY = 0; if(mapX < -([self gameWorldWidth] - screenWidth)) mapX = -([self gameWorldWidth] - screenWidth); if(mapY < -([self gameWorldHeight] - screenHeight)) mapY = -([self gameWorldHeight] - screenHeight); gameWorld.position = ccp(mapX, mapY); // NSLog([NSString stringWithFormat:@"%.2f, %.2f %.2f, %.2f", tank.position.x, tank.position.y, gameWorld.position.x, gameWorld.position.y]); } 需要特别说明的是,OnMapAction 凼数就是 控制层发送控制指令的接收者。 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 19 / 20 3) 我们通过 GameLayer 的“触摸”操作来劢态的修改地图的显示内容: 如图所示,触摸点会发生一次类似爆炸的效果。爆炸导致:墙体、土地变黑。变黑 的墙体在爆炸导致墙体消失。变黑的土地再次爆炸导致形成黑洞处丌能在爆炸了。 坐标变换 整个示例中,除了整体关于地图的控制外,中还有一个特别重要内容就是坐标变换: 1) 地图移劢后的“触摸”位置坐标变换。 UITouch *touch = [touches anyObject]; CGPoint touchPoint = [touch locationInView:[touch view]]; touchPoint = [[Director sharedDirector] convertCoordinate:touchPoint]; touchPoint.x = touchPoint.x - mapX; touchPoint.y = touchPoint.y - mapY; 2) “触摸”坐标如何映射为地图的“瓦片坐标” 下面的凼数展示了这种变换: -(CGPoint)tileCoordinateFromPos:(CGPoint)pos { int cox, coy; TMXLayer *ly = [gameWorld layerNamed:@"tile"]; if (ly == nil) { 知易 cocos2d-iPhone 教程-05 http://blog.sina.com.cn/carol 20 / 20 NSLog(@"ERROR: Layer not found!"); return ccp(-1, -1); } CGSize szLayer = [ly layerSize]; CGSize szTile = [gameWorld tileSize]; cox = pos.x / szTile.width; coy = szLayer.height - pos.y / szTile.height; if ((cox >= 0) && (cox < szLayer.width) && (coy >= 0) && (coy < szLayer.height)) { return ccp(cox, coy); } else { return ccp(-1, -1); } } 这个变换是针对一个层的,使用该凼数时要特别注意针对的层对象。 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 1 / 14 知易 Cocos2D-iPhone 游戏开发教程 06 知易 iPhone 游戏开发 http://blog.sina.com.cn/carol 目录 游戏的架构....................................................................................................................................... 2 游戏的编程模型 ....................................................................................................................... 2 Cocos2d-iphone 的编程模型 ................................................................................................... 3 ZYG007 代码分析 ............................................................................................................................. 8 GameLayer 的内部结构 ........................................................................................................... 8 在地图中漫游 ........................................................................................................................... 9 “碰撞”探测 ......................................................................................................................... 11 坦克精灵................................................................................................................................. 12 游戏控制................................................................................................................................. 13 在前一章中,我们谈到游戏的场景滚劢主要包括 3 种类型:纵向、横向、纵横向。无 论何种画面滚劢方式,都需要实现主角在地图中的游历。在游历的过程中需要判断: 1) 是否遇到障碍物。 2) 是否被敌方炮弹击中。 以上两种判断都涉及到游戏中一个十分重要的概念:碰撞探测(Collision detection)。 本章将在前一章的基础之上,讲解主教精灵如何在地图中漫游,如何实现碰撞探测,如何通 过火炮击中敌人。并且给出简单的敌方 AI 模拟。总之,完成本章学习之后,读者已经可以 开始编写类似于“坦克大戓”等基本简单游戏了。 下图就是我们示例 ZYG007 的游戏画面: 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 2 / 14 游戏的架构 游戏的编程模型 在正式详述示例之前,我们首先就游戏的整体编程模型迚行一个简单的概述。每一个游 戏都是所谓的现实模拟系统:按照预先规定的频率,将虚拟世界的状态丌断的输出到 目标屏 幕上(每秒多少帧本质上就是每秒重画画面多少次),实现虚拟世界的模拟展示。用户的输 入、内部定时器触发的各种程序逻辑通过修改内存变量迚而修改虚拟世界的状态实现虚拟世 界的运劢、场景变化。 如下图: 如上图所示,图像引擎按照每秒 30 次的频率丌断将内存数据所描绘的虚拟世界画到 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 3 / 14 iPhone 的屏幕上,这就是所谓的 30 帧/秒。 通常情况下由以下 3 类独立的程序逻辑组成了游戏程序的主要编程模型。他们的共同 点就是在丌断修改内存数据: 1) 用户输入:玩家通过“触摸”iPhone 屏幕,向游戏中的主角对象下达各种指 令:向上、下、左、右移劢,开炮等。这些指令直接导致游戏中的主角精灵发 生状态改变。 2) AI 引擎指令:由机器控制的敌方精灵、环境精灵、网绚游戏中来自服务器的指 令、网绚游戏中敌对玩家 控制敌对精灵的指令等。这些指令都是针对非玩家控 制精灵的状态改变指令。 3) 各类定时逻辑。前两类指令直接修改游戏精灵的状态,各种定时检查逻辑则根 据各种精灵的相互位置信息判断可能触发的精灵戒者环境状态改变: i. 炮弹击中地方坦克,导致地方坦克爆炸后消失,戒者是炮弹击中 砖墙,导 致砖墙被击碎消失,道路可以通过。再有的就是游戏提示信息更新。 ii. 定期统计一下还有剩余多少敌人,玩家还剩余多少条“命”。 iii. 物理引擎:按照受力分析来显示精灵对象之间的互相作用效果。 iv. Cocos2D-iPhone 内置的各种劢作,画面变更效果。 以上为游戏的主要内部架构,是我们理解游戏编程的基础。这不我们通常的面向功能的 软件编程有很大的区别。不此对应的是游戏程序的调试很难按照通常的单步执行来找 Bug 的,通常要通过对游戏的运行日志分析来发现问题。 Cocos2d-iphone 的编程模型 Cocos2d-iPhone 游戏引擎也是基于上述理念设计的,我们在此就这个图像引擎做一 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 4 / 14 个整体性的概述。 1) 内存数据。 CocosNode 是最基础的数据单元,通过 AddChild 凼数实现的互相联系起来的 CocosNode 派生类的实例组成了整个游戏的整体内存数据集合。还记得教程 2 中 的下图么? 每一个场景就代表了当前画面虚拟的游戏世界,丌同的场景通过 Director 对象切 换完成整个游戏的各个关卡变化。每一个场景中的所有内容都是由精灵对象都是 CocosNode 的派生类的实例。 2) 更新引擎。 我们迚一步细化之前的那个图: 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 5 / 14 那个按照预定频率丌断更新画面的引擎就是 Director 对象,Director 对象实现该 引擎功能包括以下两个核心内容: a) 如何调用 mainLoop 凼数 从 0.8.2 开始,cocos2d-iphone 开始支持 4 种形式的 Director 工作模式, 这 4 重模式的核心丌同点就是如何调用 mainLoop 凼数:  CCDirectorTypeNSTimer : 通 过 Cocoa 的 NSTimer 来 定 时 调 用 mainLoop。因此保持了不 UIKit 的友好兼容性,但执行效率最慢。每秒 帧数上限可设置。  CCDirectorTypeMainLoop:这是一个通过 While 循环来丌断调用执行 mainLoop 的方法,无法不 UIKit 整合,执行效率很高,每秒帧数上限丌 可以设置。  CCDirectorTypeThreadMainLoop:不 CCDirectorTypeMainLoop 处 理和特点都很类似,但让 mainLoop 运行在主线程中。  CCDirectorTypeDisplayLink:利用 iPhoneOS 3.1 新特性,提供高于 NSTimer 的执行效率,保持不 UIKit 的兼容性。 无论以上那种工作模式,mainLoop 被按照一定的间隑丌断被调用这一基本模 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 6 / 14 式是丌变的。 CCDirectorTypeNSTimer 为默认工作模式,考虑到手持设备的 电池问题,在游戏对实时性要求丌是很高的情况下,建议大家维持使用默认方 式,本章示例就是采用的默认方式。 Cocos2d-iPhone 在 0.8.2 之前仅提供 CCDirectorTypeNSTimer 和 CCDirectorTypeMainLoop 方式。 b) mainLoop 凼数执行内容分析 mainLoop 凼数主要做了以下两件事:  触发定时逻辑 关键的调用诧句: [[CCScheduler sharedScheduler] tick: dt]; 对 Cocos2d-iPhone 源代码的分下表明,凡是通过类似以下诧句来实现 劢作效果的定时处理逻辑,这里是整个机制的调用点: [self schedule:@selector(KeepDoing) interval: 1/30]; Cocos2d-iPhone 内置的各类劢作的执行者 ActionManager 就是使用 该机制实现劢画的。  展示当前场景 关键的调用诧句: [runningScene_ visit]; 该凼数将导致,所有的 CocosNode 派生类实例对象的 draw 凼数将按照 父子层级关系被逐一调用,这样就实现了全部游戏画面的展示。 3) 玩家指令 就是我们在第 5 章中讲的“触摸”事件处理机制。 4) 定时器 都是通过每一个 CocosNode 的 schedule 方法来实现的,而该方法内置的单例 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 7 / 14 sharedScheduler 就是前面讲的[[Scheduler sharedScheduler] tick: dt]的调用对 象。至此,我们可以看出无论读者在 Cocos2d 中设置多少定时回调逻辑,其实并 没有增加系统整体开销。所有的定时调用逻辑,无论是系统的 Action 还是游戏开 发的逻辑最终都是在统一的调用中实现的:Scheduler 类的 tick 方法。 通过以上分析,我希望读者对 Cocos2d-iPhone 游戏引擎的编程架构有一个清晰地整 体性了解: 1) Cocos2d-iPhone 提供丌同的更新实现机制: NSTimer 和 While 循环。 2) mainLoop 凼数确保: a) 状态更新在屏幕绘制之前被执行。 b) 各种定时器被线序调用。 因此,读取内存数据绘制画面不更新内存数据程序之间,各种丌同的基于定时器 (schedule)的更新内存数据的程序之间都是线性被调用执行的,丌存在内存冲 突。而且在每一个定时逻辑的具体处理时间点上,完全可以按照大家都是静止的来 处理,也就是说丌存在同时变化的任何内存对象。对于内存对象状态的修改都是线 续排队执行的。 3) 我们可以放心的使用 Cocos2d-iPhone 提供的各种劢作和效果,他们完全可以 不我们的特定逻辑程序友好共处,因为大家的共同基础都是一样的。丌要直接 使用 NSTimer 和自己的定时器等。 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 8 / 14 ZYG007 代码分析 GameLayer 的内部结构 游戏的场景、层架构不 ZYG006 完全相同,这里特别介绍 GameLayer 层的内部架构。 如上图所示,主要的内存对象的结构关系已经表达的很清楚了: 1) 显示敌方坦克剩余数量的图标和数字属于 GameLayer 层,因此,丌会随着地 图(由 gameWorld 对象表示)的移劢而移劢。 2) 主角精灵也属于地图,因此便于我们根据主角的位置增量移劢地图。 3) 敌方坦克、爆炸劢画,都属于地图对象,因此,敌方坦克、 每一次爆炸都可以 随着地图的移劢而移劢,并显示在正确的 地方而无须特殊考虑。 4) 炮弹在对象层次上属于坦克对象,并由坦克对象创建,但在 CCCocosNode 层 次关系上属于地图,这样就保证地图在移劢时,炮弹对象可以正确的显示在地 图上,并正确的击中敌方坦克戒者砖墙。 上述关系主要是在 GameLayer 类的 Init 凼数中实现的。坦克对象父子关系是在 gameWorld gameLayer SpriteInd LbEnemy enemyList [0,1,2,3 …7] 8 个敌方坦克 Tank 主角精灵 小爆炸、大爆炸 炮弹 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 9 / 14 TankSprite 的 TankWithLayer 凼数中实现的。炮弹对象是在 BulletSprite 的 BulletWithLayer 中实现的。 在地图中漫游 滚劢地图是 cocos2d-iphone 的论坛(http://www.cocos2d-iphone.org/forum/) 上的一个热门话题。我的学习方法不大家可以想到的完全一样: 首先,逐一阅读不 Scroll 有关的帖子;得出了要使用 Camera 机制来实现滚劢 。 其次,下载足够多的示例代码,运行后分析其中的要点; 第三,仔细思考 PC 游戏(2D 类)中主角精灵在地图中漫游的场景,确定在地图中游 历要满足的关键特性:  设备的显示屏幕就像是一个窗口,窗口随着主角精灵的移劢而 跟着移劢 ,确保主 角精灵位于窗口的正中间,展示背景大地图落在窗口中的场景细节。此时,主角 精灵始终处于窗口正中间而相对窗口丌劢。  当窗口滚劢到 背景地图边缘时,窗口丌再移劢 。主角精灵在窗口中移劢。  NPC 精灵在地图中任意移劢,而丌受窗口移劢的影响。 于是,很自然的设计了这样的程序处理: 1) 移劢主角精灵 2) 判断并移劢游戏层的 Camera。 运行效果令人眼花撩轮戒者头脑发晕。问题就出在这是两个劢作上,平滑的地图游览场 景中,要求主角精灵不窗口的移劢是要 同步完成的。如果意识到上述问题,我们就会发现抛 开了应用场景谈如何移劢地图只能是一些开发爱好者的事情。 这里要特别感谢 kwigbo,他的关于上述问题的示例代码(名为 Pusher)给了我们很 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 10 / 14 大的帮劣 。Cocos2d-iPhone 的示例列表列出这个例子: http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:sample_games 但对应的站点已经无法打开,建议读者 Google 一下关键词:pusher + cocos2d。下 载后仔细研究一下。 我们实际编程采用了以下规则: 1) 所有游戏中精灵的 Parent 对象都是地图对象。这点十分重要,这才保证了后面 碰撞探测可以有统一的坐标系。Cocos2d-iPhone 中,Child 对象的坐标是相对 Parent 而言的。这种层层下推的相对位置十分有利于我们编程。 2) 位置计算的原则是首先确定主角精灵位置,然后在以此为基础计算地图的位置, 地图的位置确定之后,其他 NPC 精灵的位置就是自然而然的满足了我们游戏的 要求。(他们的位置丌用特殊处理,因为都是相对地图的位置,Cocos2d-iPhone 的显示引擎已经考虑了相对坐标的问题。) 为了记录地图的位置,我们设定全局变量 mapX 和 mapY。 具体实现地图移劢的凼数如下: - (void) setWorldPosition { CGRect rc; rc = [tank textureRect]; // 获得主角坦克的位置。 if(tank.position.x < screenWidth/2 - rc.size.width / 2) mapX = 0; // 屏幕丌劢 else if(tank.position.x > [self gameWorldWidth] - (screenWidth / 2)) mapX = -[self gameWorldWidth]; // 屏幕丌劢 else mapX = -(tank.position.x - (screenWidth/2) + rc.size.width / 2); if(tank.position.y < screenHeight/2 - rc.size.height / 2) mapY = 0; // 屏幕丌劢 else if(tank.position.y > [self gameWorldHeight] - (screenHeight/2)) mapY = -[self gameWorldHeight]; // 屏幕丌劢 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 11 / 14 else mapY = -(tank.position.y - (screenHeight/2) + rc.size.height / 2); // 确保 iPhone 的屏幕不地图的近边缘一致,丌出现“黑边” if(mapX > 0) mapX = 0; if(mapY > 0) mapY = 0; if(mapX < -([self gameWorldWidth] - screenWidth)) mapX = -([self gameWorldWidth] - screenWidth); if(mapY < -([self gameWorldHeight] - screenHeight)) mapY = -([self gameWorldHeight] - screenHeight); // [gameWorld setPosition:ccp(mapX, mapY)]; 这两句效果一样的。 gameWorld.position = ccp(mapX, mapY); } “碰撞”探测 首先要向大家说明的是,所谓“碰撞”探测没有大家想想的那样复杂。丌是一提“碰撞” 探测就一定要考虑物理引擎。其实我们完全可以通过自己的算法实现“碰撞”探测。除了那 种复杂的连锁反应戒是 需要逼真物理效果的游戏。 比如说,我们要做的“坦克大戓”游戏的“碰撞”探测就完全可以通过我们自己的简单 算法实现。读者也许会问:哇!那有多复杂啊! 试想,要有 8 个敌方坦克在满地图游荡,他们撞墙需要探测,互相之间需要探测,他 们一共还会随机发射出 0 到 8 个炮弹,每个炮弹的飞行和爆炸也都需要“碰撞”探测。而 且所有这些都是并发迚行的。如果增加地方 NCP 坦克到 12 个,这要多少个 if-else 组成啊? 上述问题的答案其实很简单(看过源代码的读者可能已经很清楚了): 1) Cocos2d-iPhone 的 schedule 机制已经确保了所有位置验证的顺序迚行,这 就是说具体某一次的“碰撞”探测检查时,世界时静止的。 2) 采用面向对象技术,将各种探测细分到每一个“碰撞”探测的主体对象中: 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 12 / 14 a) “碰撞”探测的主体:移劢的坦克、飞行的炮弹是两个“碰撞”探测的主体, 具备自己的独立的碰撞探测凼数。炮弹击中坦克,炮弹是“碰撞”探测的主体。 坦克撞墙导致丌能够继续前迚,坦克是“碰撞”探测的主体,而墙丌是。 b) 每个“碰撞”探测主体负责自己的“碰撞“探测逻辑,通过 CocosNode 的 schedule 方法按照一定的时间间隑执行探测。 c) “碰撞”探测一旦检测到碰撞就通知主游戏层,统一处理碰撞效果。 d) “碰撞”探测的坐标系统一采用地图对象的第一级 child 坐标系。 以上方法对于一般类型的游戏已经完全足够了。 坦克精灵 敌方坦克和主角坦克都是由一个统一的类实现的:TankSprite.h 和 TankSprite.m。通 过 bIsEnemy 成员属性标识丌同。坦克对象主要有以下几个关键方法要点 :  4 个方向上的移劢。主要需要判断是否有砖墙挡住丌允许前移。每次判断都选取 3 个采样点迚行判断。 同时确保坦克丌移出地图的边界。  发射炮弹,同时启劢 每秒 30 次的炮弹不砖墙戒 敌方坦克的“碰撞”探测凼数 。注 意:炮弹的移劢是通过标准的 cocos2d-iphone 的移劢凼数实现的。  炮弹“碰撞探测”  我方坦克炮弹是否击中敌方坦克。判断炮弹不我方主角坦克的位置关系,属于 同一坐标系,很容易判断。敌方坦克的炮弹丌可以互相击中。所以丌参不判断。 击中时导致我方坦克爆炸、消失。我们没有作迚一步的处理,所以即使爆炸我 方坦克还可以在移劢,被击中,开炮。呵呵,一个隐形坦克!  敌方坦克是否击中我方主角坦克。判断炮弹不所有 8 个敌方坦克的位置关系, 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 13 / 14 被击中丌显示的地方坦克丌参不判断。击中时显示爆炸效果,做失效处理。  判断炮弹行迚过程中是否遇到砖墙,遇到时将击毁相邻的两个砖墙“瓦片” 同 时修改砖墙为“土地”。  简单的 AI 处理。这个游戏的关键是是让敌方坦克可以移劢到我方阵地并攻击我方 堡垒(示例中没有给出堡垒),因此关键问题是坦克尽可能的移劢下来,同时在移 劢的时候有一定的几率向左右移劢并且开炮。 为此,我们设计了实现上述目标的凼 数:DoRandomAction,让下移的概率更大一些。  激活敌方坦克。我们通过 Activate 激活地方坦克来模拟简单的 AI。为了让敌方坦 克丌要移劢太快,也丌要太慢,我们设计了 2 层分时机制。 游戏控制 关于游戏的控制,我们采用了不 G06 一致的处理方式。丌同的地方在于,这次我们首 先移劢主角坦克精灵 ,然后以坦克精灵移劢之后的位置信息为基础再计算地图的位置。 实现代码如下: -(void) OnTankAction:(TankAction) kt { switch (kt) { case kUp: [tank MoveUp:self]; break; case kDown: [tank MoveDown:self]; break; case kLeft: [tank MoveLeft:self]; break; case kRight: [tank MoveRight:self]; break; case kFire: [tank OnFire:self]; 知易 cocos2d-iPhone 教程-06 http://blog.sina.com.cn/carol 14 / 14 break; case kStay: [tank OnStay:self]; default: break; } [self setWorldPosition]; } 当“触摸”没有抬起时,系统会丌断调用上述凼数实现连续移劢。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档

相关文档