iPhone游戏开发入门经典 - 也适用于iPad

zhangyiwen

贡献于2013-01-09

字数:0 关键词: iPhone开发 移动开发

移动与嵌入式开发技术 iPhone 游戏开发入门经典 —— 也适用于 iPad (美) Peter Bakhirev PJ Cabrera Ian Marsh 等著 郑思遥 译 北 京 Peter Bakhirev, PJ Cabrera, Ian Marsh, et al. Beginning iPhone Games Development EISBN:978-1-4302-2599-7 Original English language edition published by Apress, 2855 Telegraph Avenue, #600, Berkeley, CA 94705 USA. Copyright © 2010 by Apress L.P. Simplified Chinese-Language edition copyright © 2011 by Tsinghua University Press. All rights reserved. 本书中文简体字版由 Apress 出版公司授权清华大学出版社出版。未经出版者书面许可,不得以任何方 式复制或抄袭本书内容。 北京市版权局著作权合同登记号 图字:01-2010-6236 本书封面贴有清华大学出版社防伪标签,无标签者不得销售。 版权所有,侵权必究。侵权举报电话:010-62782989 13701121933 图书在版编目(CIP)数据 iPhone 游戏开发入门经典—— 也适用于 iPad / (美) 巴哈雷夫(Bakhirev,P.),(美) 卡布雷拉(Cabrera,P.J.) 等著; 郑思遥 译. — 北京:清华大学出版社,2011.6 (移动与嵌入式开发技术) 书名原文:Beginning iPhone Games Development ISBN 978-7-302-25449-2 Ⅰ. i… Ⅱ. ①巴… ②卡… ③郑… Ⅲ. 移动电话机— 游戏— 程序设计 Ⅳ. TN929.53 中国版本图书馆 CIP 数据核字(2011)第 078887 号 责任编辑:王 军 谢晓芳 王滋润 装帧设计:孔祥丰 责任校对:胡雁翎 责任印制: 出版发行:清华大学出版社 地 址:北京清华大学学研大厦 A 座 http://www.tup.com.cn 邮 编:100084 社 总 机:010-62770175 邮 购:010-62786544 投稿与读者服务:010-62776969,c-service@tup.tsinghua.edu.cn 质 量 反 馈:010-62772015,zhiliang@tup.tsinghua.edu.cn 印 刷 者: 装 订 者: 经 销:全国新华书店 开 本:185×260 印 张:37.25 字 数:907 千字 版 次:2011 年 6 月第 1 版 印 次:2011 年 6 月第 1 次印刷 印 数:1~4000 定 价:79.80 元 产品编号: 作 者 简 介 Peter Bakhirev 是一位经验丰富的软件开发人员,在 Internet 技术和网络编程方面有十多年 的经验,而且还是一名有抱负的作家和企业家。在 iPhone 出现之前,他帮助设计并实现了最大 的在线纸牌网站之一。最近,他参与开发了 iPhone 的第一批多玩家游戏中的一款,该游戏名为 Scramboni。 PJ Cabrera 是一名拥有 12 年以上多行业信息系统开发经验的软件工程师,使用过的语言 包括 C、C++、Java、PHP、Python、Ruby 和 Objective-C。他住在旧金山湾区,在旧金山一家 机密的创业公司从事 iPhone 和 Rails 开发工作。 Ian Marsh 是独立游戏工作室 NimbleBit 的合伙创始人,这间工作室位于加利福尼亚州的 圣地亚哥市。自 App Store 出现以来,他就一直在为 iPhone 开发游戏,他的成功作品包括排名 第一的儿童游戏“Scoops”和排名第一的免费游戏“Hanoi”。在开发游戏之外,Ian 喜欢阅读有 关科学的书籍,喜欢发布有关游戏开发的微博,还喜欢信手涂鸦。 20 世纪 70 年代,Apple II 发布后不久,Scott Penberthy 就开始编写代码了。他对编写软 件的热爱随着 MIT 的奖学金一同绽放。在 MIT 大学,他编写了一个多玩家的在线游戏,让学 校古老的计算机不堪重负。毕业之后,Scott 在 IBM 研究院获得了一份工作,IBM 的 Web 产品 和服务皆诞生于此。在 20 世纪 90 年代构建大型网站的职位上升迁几年之后,2005 年他毅然选 择离开,回到了他热爱的编程事业。现在作为一名成功的企业家,Scott 正在运营纽约市的一家 App 开发工作室。 Ben Britten Smith 已经在 Apple 平台上从事了 15 年的软件编写工作。最值得一提的是, 他还曾经因为他的故事片作品和基于 Mac 的悬挂照相机控制系统获得过奥斯卡技术成就奖。之 后,他把他的注意力从大屏幕转向了小屏幕。 他的第一款 iPhone 游戏“SnowDude”在 SDK 发布后几个月就登上了 App Store。从此以 后,他就为多个客户开发了数十款应用程序,包括 Snowferno 游戏、“Mole - A quest for the Terracore Gem”获奖作品,以及“Gambook Adventure”系列游戏。Ben 和他的妻子 Leonie 生 活在澳大利亚的墨尔本。 iPhone 游戏开发入门经典—— 也适用于 iPad II 当感到充满了泡面和使用政府补贴的公交卡的生活方式过于奢侈的时候,Eric Wing 从加 州大学圣地亚哥分校获得计算机工程专业的硕士学位毕业了,也就是“9· 11”事件的前几天。 在接下来充满挑战的世界中,他做过各种各样的工作,从卫星系统的自动化测试,到使用大量 不同操作系统和编程语言的科学可视化。然而,由于他非凡的才能,他觉得没有钱的工作才能 让他更为努力地工作,因此他开始致力于开源项目。他是很多开源项目的贡献者,例如 SDL(Simple DirectMedia Layer)、OpenSceneGraph,以及 Lua/Objective-C Bridge(及其后继项目 LuaCocoa)。当他被邀请合著本书时,他怎么会拒绝这个几乎没有报酬且更困难的工作呢?这简 直就是天作之合! 关于技术审校者 本书的各个章节是由本书的 6 个作者相互审阅的。 Peter Bakhirev 审阅了 Ian Marsh、PJ Cabrera和Ben Britten Smith 撰写的章节。Eric Wing审阅了 Peter Bakhirev撰写的章节。Ben Britten Smith 负责审阅了 Scott Penberthy 撰写的章节。 致 谢 Peter Bakhirev 撰写这本书是一个很多年前开始的一个旅途中的一部分。在很多比我优秀、比我善良、比 我聪明的人的帮助下,这段旅途才得以顺利完成。谢谢我的父亲,谢谢您为我组装了第一台计 算机,谢谢您给我 Basic 方面的磁带,谢谢您的智慧(以及所有的游戏)。谢谢 Mike,谢谢您给 了我参加面试的机会,谢谢您教会了我现在所知道的全部编程知识,谢谢您一直以来的支持(您 总是说“我们应该找时间再来一次”)。谢谢 Lenny,谢谢您相信我能够做得到(否则我还得过 着朝九晚五的工作,而没有时间撰写这本书了)。谢谢我的母亲和 Olya,谢谢你们的鼓励,谢 谢你们每天都关心这本书的进度。Lena,谢谢你的信任,我爱你。谢谢 Keith Shepherd 把我介 绍给 Clay 和 Apress 出版社。谢谢 Clay Andres、Kelly Moritz、Douglas Pundick、Marilyn Smith, 以及 Apress 出版社的所有员工,没有您们的帮助这本书不可能顺利出版。 PJ Cabrera 我要感谢 Peter Bakhirev、Jeff Pepper 和 Marilyn Smith,谢谢你们对我撰写的章节提供的帮 助。特别要感谢 Kelly Moritz 和 Dominic Shakeshaft,谢谢你们的耐心。非常感谢 Clay Andres 和 Douglas Pundick 的支持。最后,感谢 SNAP 团队的好兄弟们 Kevin、Lenny、Ernesto、Cesar、 Alejandro、Javier、Xavier 以及 Edwin。 Ben Britten Smith Ben 要感谢 Tin Man Games 的 Neil,谢谢他允许我使用他的素材。 Eric Wing 我要向那些审阅我的章节的伟大志愿者们表示绵绵不断的谢意。 首先,我要感谢 Daniel Peacock 和 Ryan Gordon,他们利用 OpenAL 和通用音频方面的专 业技术知识帮我找出了所有的错误和纰漏。还要谢谢 Ian Minett 提供了 Xbox 的 OpenAL 实现的 细节,以及 Garin Hiebert 为我找出了一些 OpenAL 方面的模糊信息。 接下来我想要感谢 Josh Quick、Victor Gerth、Carlos McEvilly 和 Wang Lam 以一般读者的 角度来审阅我的章节,让我在把书提交到出版社之前得以改进很多内容。 最后,还有一些不愿意透露姓名的人。不论如何我都要感谢他们,因为他们的贡献太大了。 当然,我还要感谢我的合作者们,以及 Apress 的人,谢谢他们的支持。 前 言 好奇的读者,你们好!我是 Peter,我想向您介绍一下我的合著者 Ian、PJ、Scott、Ben 和 Eric。我们聚在一起只有一个原因:为了帮助您学习如何开发出色的 iPhone 游戏。您也许注意 到了本书书名中的“入门”二字。下面是对入门的解释:您可以开发游戏,即使以前从来没有 把自己当作“游戏开发人员”。尽管“开发游戏”一开始看上去很高深的样子(像火箭科学一样), 可实际上它并非如此(尽管本书要构建的一款游戏包含一枚火箭,以及壮观的爆炸效果,还包含 外太空的声音和邪恶的外星人)。我们相信,任何人都能学到有用的技能,我们会帮助您循序渐 进,并解释遇到的所有内容。 说到创意和游戏,我们有很多可以玩的游戏。本书包含了 6 款完全可玩的游戏,这些游戏 有助于您的开发工作。在学习的过程中,您会懂得如何构建带有音乐、音效且支持多玩家和网 络的 2D 和 3D 游戏。但是“构建什么”至少与“如何构建”的问题一样重要,您也会找到很 多有关如何设计好玩的游戏的讨论。 如果您以前从来没有从事过 iPhone 的开发工作,则需要一本速成教程,用于学习如何使用 开发工具,我们也对 Objective-C 和 Xcode 做了简单的介绍。不过我们强烈推荐 Dave Mark 和 Jeff LaMarche 合著的 Beginning iPhone 3 Development 一书,在这本书中您可以更加深入地学习 iPhone开发环境。如果您需要详细了解程序设计(特别是 C 语言和 Objective-C 语言)的相关内容, 可以参阅 Dave Mark 撰著的 Learn C on the Mac 一书以及 Mark Darlymple 和 Scott Knaster 合著 的 Learn Objective-C on the Mac 一书。 本书的编写、编码和调试的过程充满了乐趣,我们也希望您能够发现其中的乐趣并且参与 到其中。祝您好运,在 App Store 中见! 作者团队敬上 Peter Bakhirev 目 录 第 1 章 革命性的游戏平台:随时随地, 人人都可以游戏 ........................1 1.1 无处不在的 iPhone ...................... 1 1.2 iPhone 的广泛吸引力——每时 每刻都在诞生新的玩家............... 2 1.3 iPhone 的用户界面——手柄终 结者 .............................................. 3 1.4 iPhone 的连接性——和其他 玩家一起玩 .................................. 4 1.5 iPhone中的用户数据——个性化 体验 .............................................. 5 1.6 iPhone 设备的性能——强大的 多媒体设备 .................................. 6 1.7 iPhone 的开发包——人人皆可 拥有 .............................................. 7 1.8 创新——来自小开发商的 大创意 .......................................... 8 1.9 本章小结 ...................................... 9 第 2 章 iPhone 游戏开发:iPhone 工具箱一览..............................11 2.1 开发工具和开发环境 ................ 11 2.2 UIKit........................................... 12 2.3 Quartz 2D 和 Core Animation.... 13 2.4 OpenGL ES................................. 13 2.5 音频 API..................................... 14 2.6 网络 ............................................ 15 2.7 本章小结 .................................... 16 第 3 章 在小屏幕上移动图像 ——使用 UIKit 控件................................17 3.1 Cocoa Touch 简介 ...................... 17 3.1.1 Objective-C 语言...................18 3.1.2 Cocoa Touch 和 UIKit 框架......................................25 3.2 构建一款简单的游戏 ................27 3.2.1 创建一个 Xcode 项目 ..........27 3.2.2 创建 IVBricker 用户 界面......................................29 3.2.3 华丽的图像才够格 ...............34 3.2.4 完成了吗 ..............................57 3.3 应用程序委托事件 ....................57 3.3.1 应用程序终止.......................58 3.3.2 应用程序中断.......................58 3.3.3 低内存警告...........................59 3.4 保存和加载游戏状态 ................59 3.5 动画图像 ....................................63 3.5.1 使用 UIImageView 的动画 属性......................................63 3.5.2 通过 NSTimer 实现动画.......64 3.5.3 通过 CADisplayLink 实现 动画......................................66 3.6 本章小结 ....................................68 第 4 章 射击、命中与得分...................71 4.1 Quartz 2D 游戏概述...................71 4.2 所有的艺术家都需要画布 ........73 4.3 用 Quartz 2D 创建第一个 图形 ............................................79 4.3.1 保存和还原上下文 ...............80 4.3.2 添加颜色 ..............................82 4.4 Sprites .........................................83 4.4.1 创建 Sprite 类 .......................83 iPhone 游戏开发入门经典—— 也适用于 iPad X 4.4.2 使用 Sprite 类 .......................89 4.5 哪一面朝上 ................................ 91 4.5.1 切换到水平方向...................91 4.5.2 将原点居中...........................93 4.6 矢量图 ........................................ 94 4.6.1 创建 VectorSprite 类.............94 4.6.2 使用 VectorSprite 类.............97 4.7 翻页动画 .................................. 100 4.7.1 创建 AtlasSprite 类 .............101 4.7.2 修改 Sprite 类 .....................105 4.7.3 使用 AtlasSprite 类 .............107 4.8 抬头显示 .................................. 110 4.8.1 创建 TextSprite 类 ..............111 4.8.2 使用 TextSprite 类 ..............116 4.9 Asteroids 游戏架构.................. 117 4.9.1 Asteroids 游戏循环.............117 4.9.2 Asteroids 模型....................118 4.9.3 Asteroids 视图....................120 4.9.4 Asteroids 游戏控制器 ........121 4.10 本章小结 ................................ 123 第 5 章 通过 Core Animation 实现 动画效果 ...............................125 5.1 Core Animation 示例项目 概述 .......................................... 125 5.2 UIView 的动画......................... 127 5.2.1 简单移动 ............................130 5.2.2 动画曲线 ............................131 5.2.3 反向和重复.........................132 5.2.4 延时、缓入和缓出 .............134 5.2.5 UIView 变换......................135 5.2.6 UIView 过渡.......................137 5.3 连续变化的 Core Animation 层.............................................. 139 5.3.1 隐式动画 ............................140 5.3.2 定时函数 ............................140 5.3.3 层动画过渡.........................141 5.4 本章小结 .................................. 145 第 6 章 OpenGL 基础:理解 OpenGL API .........................147 6.1 什么是 OpenGL ES 和为什么 要使用 OpenGL ES..................147 6.2 理解 3D 世界............................148 6.3 矩阵的基本概念 ......................149 6.3.1 综合平移、旋转和缩放......151 6.3.2 矩阵类型 ............................151 6.3.3 有状态的 API.....................152 6.4 渲染的基本概念 ......................153 6.5 基本游戏模板 ..........................154 6.6 将 CAEAGLLayer 包装在 EAGLView 视图中 ..................155 6.6.1 第一步:初始化方法..........155 6.6.2 帧缓冲区、渲染缓冲区和 深度缓冲区 ........................158 6.6.3 进入 OpenGL 的世界..........160 6.6.4 绘制和渲染.........................164 6.7 如何通过 OpenGL 绘制 物体 ..........................................165 6.7.1 场景对象和网格对象..........165 6.7.2 压入和弹出矩阵.................168 6.7.3 在场景中放置对象 .............169 6.7.4 在 3D 空间中定义对象.......171 6.8 游戏循环和定时器 ..................174 6.9 输入控制器 ..............................178 6.10 应用程序委托 ........................179 6.11 本章小结.................................180 第 7 章 综合所学知识:创建一款 OpenGL 游戏........................181 7.1 游戏设计——Space Rocks!.....181 7.2 以模板为基础开始进行游戏 设计 ..........................................183 7.3 屏幕翻转 ..................................184 7.4 升级为 3D 点............................186 7.5 添加按钮 ..................................188 7.5.1 创建按钮对象.....................189 目 录 XI 7.5.2 使用顶点数据.....................190 7.5.3 存储按钮 ............................192 7.5.4 触摸检测 ............................194 7.5.5 连接按钮 ............................199 7.6 构建更好的宇宙飞船 .............. 200 7.6.1 移动起来 ............................200 7.6.2 添加宇宙飞船.....................201 7.6.3 添加和删除场景对象..........203 7.6.4 飞出屏幕边缘.....................205 7.7 Space Rocks! ............................ 206 7.8 添加导弹 .................................. 209 7.8.1 发射导弹 ............................210 7.8.2 删除不需要的导弹 .............211 7.9 制作更好看的按钮 .................. 212 7.10 碰撞检测 ................................ 214 7.10.1 什么是碰撞.....................214 7.10.2 检测碰撞的技术.............214 7.11 碰撞石块 ................................ 218 7.11.1 质心和半径.....................218 7.11.2 碰撞对象和被碰撞 对象 ................................220 7.11.3 再论碰撞检测.................230 7.12 本章小结 ................................ 232 第 8 章 图册、Sprite 和粒子系统 ......233 8.1 纹理和纹理图册 ...................... 233 8.1.1 什么是纹理,为什么要 使用纹理 ............................234 8.1.2 将图像数据载入 OpenGL..............................234 8.1.3 绑定纹理 ............................238 8.1.4 使用 UV 坐标.....................239 8.1.5 获得纹理贴图的四边形......240 8.1.6 认识纹理图册.....................241 8.1.7 推陈出新 ............................245 8.1.8 更好的用户界面.................246 8.1.9 带有纹理的颜色.................248 8.2 Sprite 动画................................ 249 8.2.1 与帧率无关的设计 .............251 8.2.2 其他动画 ............................255 8.3 从 2D 迈向 3D..........................256 8.3.1 多了一个维度意味着 什么....................................256 8.3.2 3D 模型从何而来 ...............257 8.3.3 从建模工具到屏幕显示......258 8.3.4 什么是法线.........................258 8.3.5 标准化为 GL_TRIANGLES................259 8.3.6 纹理加上模型.....................260 8.3.7 影子展示出形状.................261 8.3.8 深度缓冲区和背面剔除......264 8.3.9 碰撞检测的改进.................266 8.4 粒子系统给游戏带来了 生机 ..........................................267 8.4.1 什么是粒子系统.................267 8.4.2 大量随机数.........................268 8.4.3 粒子系统的根本:粒子......268 8.4.4 粒子发射器.........................269 8.4.5 系统调整 ............................276 8.4.6 其他对象的粒子系统..........277 8.4.7 What the Future Holds: Shaders ...............................278 8.5 本章小结 ..................................278 第 9 章 核心音频简介........................279 9.1 核心音频提供的音频服务 ......279 9.1.1 音频单元 ............................280 9.1.2 音频文件服务.....................280 9.1.3 音频文件流式服务 .............280 9.1.4 音频转换服务.....................280 9.1.5 扩展的音频文件服务..........281 9.1.6 音频会话服务.....................281 9.1.7 系统声音服务.....................281 9.1.8 音频队列服务.....................281 9.1.9 AVFoundation.....................281 9.1.10 OpenAL ............................282 9.2 核心音频框架 ..........................283 9.3 编解码器和文件格式 ..............284 iPhone 游戏开发入门经典—— 也适用于 iPad XII 9.3.1 核心音频所支持的 编解码器 ............................284 9.3.2 核心音频支持的文件 格式....................................285 9.3.3 通过 afconvert 转换 格式....................................286 9.3.4 硬件加速的编解码器:受限 和非受限的编解码器组......286 9.3.5 有关编解码器和文件格式 的建议 ...............................287 9.4 警告和振动:系统声音服务 介绍.......................................... 288 9.4.1 不使用系统声音服务作为 通用音效的情形.................289 9.4.2 系统声音服务示例 .............290 9.4.3 关于异步播放的注意 事项....................................295 9.5 设置音频策略:音频会话服务 介绍.......................................... 296 9.5.1 使用音频会话服务的样本 代码和过程 ........................297 9.5.2 检测硬件并获得属性..........299 9.6 通过 AVFoundation 利用 Objective-C 轻松播放音频 ..... 300 9.7 本章小结 .................................. 310 第 10 章 通过 OpenAL 发出声音.......311 10.1 OpenAL 概述 ......................... 311 10.1.1 OpenAL 支持的特性......311 10.1.2 OpenAL 简史..................312 10.2 Eric Wing 的故事和音频涵盖 的内容..................................... 315 10.3 内容指引 ................................ 316 10.4 在 OpenAL 中设置基本 声音......................................... 316 10.4.1 设置音频会话.................318 10.4.2 打开设备 ........................318 10.4.3 创建上下文.....................321 10.4.4 激活上下文.....................322 10.4.5 生成声音源.....................322 10.4.6 生成数据缓冲区.............323 10.4.7 从文件中加载声音 数据 ................................323 10.4.8 将声音数据提交至 OpenAL 数据缓冲区.......327 10.4.9 将数据缓冲区关联至 声音源 ............................329 10.4.10 播放声音.......................329 10.4.11 关闭并清理...................329 10.5 更多问题和细节补充 ............331 10.5.1 添加更多的声音.............332 10.5.2 需要注意的问题.............336 10.5.3 OpenAL 错误检查 .........336 10.5.4 音频会话中断.................338 10.5.5 iOS 的 OpenAL 扩展 ......341 10.5.6 性能说明 ........................341 10.5.7 OpenAL 声音源的限制 ....342 10.6 声音资源管理器:修复 设计.........................................344 10.6.1 资源管理器概述.............345 10.6.2 初步清理 ........................346 10.6.3 声音文件数据库(缓存 系统)...............................352 10.6.4 OpenAL 声音源管理(预留 和回收) ...........................356 10.6.5 与 Space Rocks!游戏的 集成 ................................360 10.6.6 处理所有可用的声音源 都耗尽的情况.................370 10.6.7 最后的润色.....................371 10.7 本章小结 ................................373 第 11 章 3D 音频——将声音变为游戏 声音 .....................................375 11.1 OpenAL 的设计:声音源、 缓冲区和听者.........................375 11.2 OpenAL 中 3D 音频的 局限性.....................................377 目 录 XIII 11.3 将听者整合进 Space Rocks! 游戏 ........................................ 378 11.3.1 创建听者类.....................378 11.3.2 挑选听者.........................379 11.4 在声音中添加位置 ................ 380 11.4.1 处理创建时的初始位置 ...382 11.4.2 禁用距离衰减 .................383 11.5 听者朝向 ................................ 384 11.5.1 右手坐标系统和右手 定则 ................................385 11.5.2 单位圆、极坐标转换直角 坐标、相位平移和三角恒 等式 ................................386 11.5.3 集成至 Space Rocks! 游戏 ................................388 11.6 声音源方向和圆锥 ................ 389 11.6.1 内圆锥、外圆锥和 过渡区 ............................390 11.6.2 实现 ................................391 11.7 速度和多普勒效应 ................ 392 11.7.1 速度和缩放因子 .............393 11.7.2 多普勒效应示例 .............394 11.8 距离衰减 ................................ 396 11.8.1 衰减模型.........................396 11.8.2 回到 Space Rocks! 游戏.................................401 11.9 使用相对声音属性选择性的 禁用 3D 效果.......................... 404 11.10 本章小结 .............................. 406 第 12 章 流式媒体 .............................409 12.1 音乐与其他音频 .................... 409 12.2 iPod 音乐库(Media Player 框架)....................................... 411 12.2.1 在 Space Rocks!游戏中 播放 iPod 音乐................413 12.2.2 添加媒体项选择器 .........415 12.2.3 摇晃(简易的加速度 传感器摇晃检测)............417 12.3 音频流式处理 ........................418 12.3.1 基于 AVFoundation 的 Space Rocks!游戏背景 音乐 ................................419 12.3.2 OpenAL 缓冲区队列 介绍 ................................424 12.3.3 基于 OpenAL 的 Space Rocks!游戏背景音乐 ......432 12.3.4 Space Rocks!游戏的 OpenAL 语音 ..................445 12.3.5 基于音频队列服务的 Space Rocks!游戏背景音乐 ......454 12.3.6 完美的全连.....................456 12.4 音频捕捉 ................................456 12.4.1 音频捕捉 API .................457 12.4.2 AVFoundation:使用 AVAudioRecorder 录制 到文件 ............................458 12.4.3 OpenAL:捕捉示波器.....463 12.5 回到 OpenGL .........................468 12.5.1 顶点缓冲区对象.............469 12.5.2 有关 OpenGL 和 OpenAL 优化的注意.....................470 12.6 本章小结 ................................471 第 13 章 iPhone 游戏联网简介..........473 13.1 了解网络 ................................473 13.1.1 网络接口 ........................473 13.1.2 TCP/IP ............................474 13.1.3 Bonjour ...........................475 13.2 iPhone SDK 和联网 ...............475 13.2.1 套接字和连接.................475 13.2.2 BSD 套接字 API.............476 13.2.3 CFNetwork......................476 13.2.4 NSNetServices.................476 13.2.5 GameKit..........................476 13.3 本章小结 ................................477 第 14 章 对战游戏 .............................479 14.1 见识 Pong ...............................479 iPhone 游戏开发入门经典—— 也适用于 iPad XIV 14.2 使用 Peer 选择器寻找一个 人类对手 ................................ 480 14.2.1 运行界面 ........................484 14.2.2 工作原理 ........................487 14.3 建立连接 ................................ 487 14.4 发送和接收消息 .................... 491 14.4.1 投掷骰子 ........................492 14.4.2 准备好,继续.................500 14.4.3 击球和失球.....................501 14.4.4 让板子活跃起来.............510 14.5 游戏结束:处理断开 连接........................................ 515 14.6 本章小结 ................................ 516 第 15 章 多人游戏 .............................517 15.1 8乘以 3 等于几 ..................... 517 15.1.1 起始点 ............................517 15.1.2 目标................................519 15.1.3 结构................................519 15.2 创建连接 ................................ 521 15.2.1 连接对象和流对象的 简介 ................................521 15.2.2 连接初始化.....................522 15.2.3 关闭和清理.....................524 15.2.4 读取数据 ........................524 15.2.5 写入数据 ........................527 15.2.6 处理流事件.....................528 15.2.7 完整画面 ........................529 15.3 套接字服务器 ........................ 529 15.3.1 SocketServer 类...............530 15.3.2 套接字服务器初始化......531 15.3.3 通过 Bonjour 发布服务....534 15.3.4 开始和停止.....................534 15.4 通过 Bonjour 查找服务 ......... 536 15.4.1 查找服务器.....................536 15.4.2 连接至服务器.................538 15.4.3 最后的细节.....................540 15.5 实现游戏客户端 .................... 544 15.5.1 跟踪逻辑 ........................544 15.5.2 选择网络消息的格式......546 15.5.3 实现通信 ........................548 15.5.4 整合起来 ........................551 15.6 实现游戏服务器 ....................552 15.6.1 管理玩家 ........................552 15.6.2 代码整合 ........................555 15.6.3 初始化 ............................561 15.6.4 玩家加入和离开.............561 15.6.5 开始和结束游戏回合......563 15.6.6 收集和处理答案.............565 15.6.7 整合起来 ........................566 15.7 本章小结 ................................566 第 16 章 连接 Internet .......................567 16.1 挑战 ........................................567 16.1.1 不能使用 Bonjour ...........567 16.1.2 不能通过 GameKit 实现 点到点的游戏.................568 16.1.3 服务器中断.....................568 16.1.4 滞后................................568 16.1.5 数据格式问题.................569 16.1.6 其他问题 ........................569 16.2 在线游戏基础知识 ................570 16.2.1 客户端/服务器游戏 ........570 16.2.2 不通过 Bonjour 连接到 游戏服务器.....................571 16.2.3 点到点的游戏.................571 16.2.4 有关重新开发.................572 16.2.5 沧海一粟 ........................573 16.3 引入社交元素 ........................573 16.3.1 在线共享最高得分 .........573 16.3.2 虚拟追击 ........................574 16.3.3 聊天................................575 16.4 本章小结 ................................575 第 17 章 整合一切,享受乐趣...........577 17.1 本书涵盖的内容 ....................577 17.2 一些游戏设计的技巧 ............578 17.3 本章小结 ................................578 革命性的游戏平台:随时随地, 人人都可以游戏 iPhone 平台已经极大地改变了下一代移动游戏的前景。iPhone 平台创造了许多第一,正是 iPhone 平台多才多艺的能力使得 iPhone 大大超越了传统的移动游戏平台。由于 iPhone 的独特 性、连接性、个人集成性,流行以及新颖的界面,因此 iPhone 已经成为时下最火爆、最激动人 心的开发平台。 1.1 无处不在的 iPhone 由于 iPhone 本身就是一个移动电话,而且还是数字音乐播放器,因此绝大多数 iPhone 和 iPod touch 的拥有者都会每时每刻随身携带着这个设备。由于 iPhone 将电话、音乐播放器和游 戏机整合在一个纤小的软件包内,因此人们每天早上再也不需要考虑今天要携带什么设备了。 因此,iPhone 可以称为一个开创性的游戏平台。 对于 iPhone 或 iPod touch 的用户,随时都可以打开游戏——不管在加油站等待加油还是在 飞越大西洋的航班上。随处看看,就可以发现 iPhone 正在迅速扩张而且 iPhone 游戏的市场也 在迅速扩张。对于移动中的游戏玩家,iPhone 是最适合随身携带的游戏设备了。 由于玩家随手就可以开始玩游戏,因此游戏开发人员既可以开发那种很快的“即开即玩” 的游戏,也可以开发需要耗费 30 分钟或更多时间玩的“任务”型游戏。例如,Veiled Game 开 发的 Up There(如图 1-1 所示)是一个非常好的、消磨时光的游戏,这个游戏每回只需要几分钟 的时间即可,适合不会花大段时间玩游戏的休闲玩家。另外一类玩家喜欢消耗时间玩一些非常 吸引人的游戏,例如 Electronic Art 开发的 SimCity(如图 1-1 所示)。这两类玩家都有可能拥有 iPhone,因此开发人员可以设计迎合这两类人兴趣的游戏。有一点是肯定的:作为 iPhone 游戏 的开发人员,您可以期望您的用户在每一天睁开眼睛的那一刹那就开始带着您的游戏到处跑了。 尽管用户可能会一直带着您的游戏,但这并不意味着他们会花更多的时间玩游戏,不过这 仍然可以帮助您开发游戏的市场。高质量 iPhone 游戏最好的广告方式之一就是玩家的口头传 播。iPhone 用户通常都喜欢和其他 iPhone 用户联系。他们随身携带着他们完整的游戏库,这样 玩家随时可以向其他玩家炫耀他们最喜欢的游戏,而他们的朋友也可能立即自己也购买一款。 第 章 1 iPhone 游戏开发入门经典—— 也适用于 iPad 2 图 1-1 Up There 为玩家提供了一款快速的放气球游戏。模拟游戏的爱好者们会很容易地连续消耗大量时间玩 SimCity 1.2 iPhone 的广泛吸引力——每时每刻都在诞生新的玩家 游戏并不是 iPhone 吸引用户的唯一原因。有的人主要是对 iPhone 的 Web 浏览功能和多媒 体功能感兴趣。但是即使是那些从来没有玩过视频游戏的用户也会觉得 App Store 及其成千上 万款的游戏非常有吸引力。 App Store 的易用性结合了这么多游戏的可用性,很有可能将任何人转变为游戏玩家,不管 是休闲玩家还是其他玩家。开发人员可以创建适合所有类型的人的游戏——从在车上玩游戏的 小孩,到远离他的 Xbox 的 Halo 迷,到在椅子上休闲的老爷爷。iPhone 可以让从来不会认为重 要到需要专门购买一台设备来玩游戏的人们也能接触到您的游戏。 iPhone 应用程序的多样性实际上在模糊游戏的定义。一些类似于 Snappy touch 开发的 Flower Garden、The Blimp Pilots开发的Koi Pond (如图1-2所示),以及 Bolt Creative开发的Pocket God 娱乐应用程序都非常流行。虽然这些交互式体验可能并不是字面定义的游戏,但是它们和 游戏有很多共性,也能够吸引大量的爱好者。 由于应用程序的类型是由大量开发人员决定的,而不是由发布者决定的,因此创新和实验 非常泛滥。由于存在如此多的潜在客户和不同类型的玩家,因此几乎任何类型的游戏都会有市 场——不管是经典类型的游戏还是人们从来没有见过的游戏。 作为一个 iPhone 游戏开发人员,您的潜在客户群正在日益增长,而且没有减缓的趋势。由 于支持标准化的产品型号,即使是客户升级了 iPhone 或 iPod touch,它们仍然可以继续玩您的 游戏——而这在传统的移动电话游戏中几乎是不可能的。您的游戏可以面向所有 iPhone 或 iPod touch 玩家,不用考虑它们拥有什么模型。即使是最大最成功的游戏,也不可能浸透整个市场, 因为每时每刻都在诞生新的 iPhone 玩家。 第 1 章 革命性的游戏平台:随时随地,人人都可以游戏 3 图 1-2 Flower Garden 是一款用户可以管理一个虚拟花园并向朋友送花的应用。 Koi Pond 向用户展示了一个交互式的虚拟金鱼池 1.3 iPhone 的用户界面——手柄终结者 iPhone的用户界面是这个革命性平台的另一部分。和Nintendo Wii将电视游戏(console game) 带给公众一样的方式,iPhone 通过触摸屏、加速度传感器、摄像头和麦克风让游戏开发人员可 以为交互式体验带来直观自然的界面。 通过控制倾斜、触摸和麦克风控件,开发人员可以使控件对游戏透明,创造出逼真和丰富 的用户体验。通过手指直接操作游戏对象或通过移动手中的物理设备来操作游戏对象,用户可 以得到符合感官的游戏界面。用户再也不需要花时间来学习怎样将游戏按键、游戏板或游戏手 柄对应到游戏动作了。非常明显 iPhone 的界面完全可用。 开发人员正在以全新且未知的方式利用这些非传统的控制方法。iPhone 独特的用户界面已 经派生了一些全新类别的游戏。改变设备倾斜的角度可以控制倾斜类的游戏,例如 Lima Sky 开发的 Doodle Jump 和 NimbleBit 开发的 Scoops(如图 1-3 所示)。多点触摸的游戏(例如 Igloo Games 开发的 Bed Bugs)可以使得用户专注于游戏,因为迫使用户同时操作多个游戏对象。Smule 开发的娱乐应用程序 Ocarina 和 Leaf Trombone 允许用户通过向 iPhone 的麦克风吹气来弹唱虚 拟乐器。 iPhone 向用户提供了非常吸引人且自然的交互方式,用户甚至看不见界面的存在,因此游 戏的界面本身就是游戏。可以展示 iPhone 的透明用户界面功能的一个很好的示例是 NimbleBit 开发的 Hanoi(如图 1-3 所示)。Hanoi 是一款经典的汉诺塔谜题游戏,许多计算机科学的算法课 程都会拿汉诺塔谜题作为示例。在其他很多平台上,这个简单的游戏可能会要求用户通过方向 iPhone 游戏开发入门经典—— 也适用于 iPad 4 手柄或按键选择盘片,并且在屏幕上显示被选择的盘片,然后再向用户提供放置所选盘片的操 作方法。如果要仔细思考这个过程,就会发现这比起真实世界中简单的任务仿佛像一个相对复 杂的界面。而在 iPhone 的 Hanoi 游戏中,用户可以通过更加自然的方式移动盘片:用户只需要 轻触盘片,然后将盘片拖拽到合适的位置就搞定了。不需要方向手柄、按键、游戏柄或触摸笔。 图 1-3 Scoops 通过 iPhone 的加速度传感器来控制由冰淇淋勺组成的摇晃塔。 Hanoi 的界面让用户可以很自然地对游戏对象进行拖放 1.4 iPhone 的连接性——和其他玩家一起玩 前所未有的连接性是将 iPhone 和过去其他移动游戏平台区分开的另一个功能。面向 iPhone (或 iPod touch)的开发人员可以认为他们的游戏始终拥有一个数据连接。这意味着 iPhone 上的 游戏可以使用 Internet 连接性——既可以是游戏本身的核心部分(多玩家游戏),也可以是增强离 线游戏体验的一部分(高分榜),还可以和社交网络集成,例如 Facebook 和 Twitter。例如 Com2uS 开发的 Baseball Slugger(见图 1-4)可以让您在卧室、公园长凳上或公交车座上和世界上其他任 何地方的玩家进行对战。 即使设备没有和 Internet 连接上,开发人员可以通过蓝牙来连接玩家。另外通过推送消息 的功能,甚至在玩家没有玩游戏的时候也可以参与到游戏中。并不是为非实时多玩家交互设计 的游戏,例如 ngmoco:)开发的 Rolando 2(如图 1-4 所示)通过 iPhone 的连接能力向其他玩家发 送异步的挑战。这种技术特别适合于在回合制的游戏、或对手发出挑战的时候、或玩家记录被 刷新的时候通知玩家。 设备的联网能力不仅提升了游戏本身的体验,还使得用户可以随时随地购买和下载游戏。 在传统的移动平台中,用户需要访问零售店,还得期待他们想要购买的游戏在店中有货。在过 第 1 章 革命性的游戏平台:随时随地,人人都可以游戏 5 去,客户需要耗费很多时间和精力才能购买到您开发的游戏产品。而通过实时的 App Store,用 户只需要在 iPhone 上轻触一下就可以购买游戏,因此开发人员的游戏能以前所未有的速度更轻 松地到达用户的设备上。即使现在一些其他移动游戏平台也推出了数字化发行的功能,但是 App Store 通过绑定 iTunes 账号可以避免购买游戏时候的购买或充点或充值的麻烦,购买游戏 就像买一首歌那么简单。另外,通过有偿下载内容的支持,开发人员拥有更多的方式来为他们 的创造带来价值。 图 1-4 Rolando 2 通过推送消息向其他没有在玩游戏的朋友发出挑战。 Baseball Slugger 让您可以随时随地挑战世界上其他任何地方的玩家 iPhone 的连接性给游戏开发人员带来了大量机会。他们可以创建根据实时数据或新内容动 态更新的游戏。玩家不仅可以和世界上其他地方的玩家一起游戏,还可以和他们实时语音聊天。 开发人员可以在游戏中集成任何 Internet 技术——既可以是社交网络,也可以是流式音频和视 频。开发人员甚至还可以通过记录并检索用法数据来获得用户如何玩游戏的宝贵信息。 1.5 iPhone 中的用户数据——个性化体验 iPhone 是第一个可以访问大量个人信息的游戏平台。通过访问用户的联系人、音乐、相册、 视频和位置信息,游戏开发人员可以创建非常个性化和定制化的用户体验。有哪些其他的游戏 平台能知道用户有哪些朋友?通过访问用户的联系人,iPhone 游戏可以使用好友或家人的名称 作为角色,直接和他们联系,还可以轻松开始多人对战。在 ngmoco:)开发的 Dr. Awesome 游戏(如 图 1-5 所示)中,住院病人的名称来自玩家的地址簿中的联系人,玩家可以在游戏中获得非常个 性化的体验。 iPhone 游戏开发入门经典—— 也适用于 iPad 6 图 1-5 Face Fighter 玩家可以通过 iPhone 中的照片来定制对手。 ngmoco:)开发的 Dr. Awesome 允许住院病人是玩家的联系人 iPhone 用户不仅可以将自己的音乐库随身携带,还可以从他们是喜爱的音乐中选择音乐作为 自己的游戏音轨。玩家可以通过设备上拍摄或存储的照片或视频来定制他们的游戏角色或环境。 除了这些个人信息之外,开发人员还可以访问设备的物理位置。尽管基于位置的游戏仍然 处于萌芽阶段,但是可以通过位置信息来匹配附近的玩家或以地图的方式展示全世界范围的玩 家的位置。 虽然其他设备也会简单地通过内置或外置的摄像头来允许玩家对游戏进行定制,但是大多 都不够深入,因为玩家只能使用那些采用特定的摄像头拍摄的照片。而 iPhone 上的游戏和应用 程序不仅能访问内置的摄像头,还能使用其他图像,例如从浏览器保存的图片、E-mail 中的图 像、保存在联系人中的图像,以及彩信中发送的图像。这意味着在一些游戏中,例如 Appy Entertainment 开发的 Face Fighter(如图 1-5 所示)游戏中,您可以和您的好朋友、同事或从网页 中保存其照片的明星进行功夫对战。 通过访问 iPhone 用户的音乐、相册、视频、好友和位置信息,游戏开发人员拥有前所未有 的能力访问玩家的个人生活。在合适的场合下,iPhone 游戏可以通过定制或使用一些用户个人 信息来引起更加情感化的响应,还能在游戏中给用户带来更好的归属感。如果使用恰当,访问 用户数据就是 iPhone 开发人员工具箱的另一个创造性工具。 1.6 iPhone 设备的性能— — 强大的多媒体设备 硬件性能是许多开发人员关注的内容。移动游戏平台的处理能力决定了游戏可以在这个设 备中使用的 3D 图形技术、物理模拟技术以及其他技术的限度。幸好,iPhone 在这方面一点儿 也不落后。高超的开发人员令 3GS 之前的设备性能超越了 Nintendo DS,与 Sony便携式游戏机 第 1 章 革命性的游戏平台:随时随地,人人都可以游戏 7 (PlayStation Portable,PSP)性能相当。随着 3GS 型号的发布,iPhone 的硬件能力和性能已经走 在了行业的前列。 搭载着更快的处理器、更多内存和更强劲的 3D 图形功能,未来的 iPhone 和 iPod 硬件将超 越移动游戏实现功能的极限。每一款更完善的设备都能处理更多的多边形,模拟更复杂的物理 现象。已有的开发商(例如 Electronic Arts、Gameloft 和 Firemint)已经生产出了一些达到甚至超 越类似 DS 和 PSP 游戏的 iPhone 游戏产品(如图 1-6 所示)。 图 1-6 Firemint 开发的 Real Racing 和 Gameloft 开发的 Let’s Golf 展示了平台的图形性能 由于支持常见的游戏开发技术(例如 OpenGL ES 和 OpenAL),有经验的开发人员可以快速 开发出丰富的图形体验。由于 iPhone 软件开发包(Software Development Kit,SDK)除了支持 Objective-C 之外还支持 C/C++,因此很多已有的库和游戏代码可以重用,或者可以轻松地移植 到 iPhone 平台上。 除了设备内部的强大硬件,开发人员也会很喜欢这款设备的外貌。只要一眼看到那块坚硬、 高清、玻璃覆盖的显示屏,开发人员就会忘记那些老移动游戏的廉价、微小屏幕。iPhone 的屏 幕分辨率为 320×480 像素,可以显示高质量的游戏美工,漂亮地渲染 3D 场景。显示屏的快速 响应时间可以消除图像残影,即使是以 60 帧每秒(frame per second,fps)运行的游戏也不会有问 题。光滑的玻璃外壳和内置电容触摸屏使得任何游戏中的轻触(tap)、拖动(drag)、开合双指(pinch) 和缩放(zoom)成为一种享受。 为了完善整个软件包,iPhone 硬件还向开发人员提供了强大的音频能力。通过硬件音频解 码,CPU 可以关注游戏逻辑处理,而不用忙于播放背景音乐。如果需要最高质量的无损音频, 那么也有足够的磁盘空间可以存储它们。尽管单声道的扬声器多少是个遗憾,但是开发人员可 以期待大部分用户都随身带着耳机享受逼真的音频体验。 1.7 iPhone 的开发包——人人皆可拥有 显然,作为游戏平台,iPhone 最具革命性之处在于几乎任何人都可以为 iPhone 平台开发游 戏。这个特色可以让其他特色都黯然失色。一个平台可能拥有业界最具开创性的功能特色,但 是如果为开发人员设立了太高的门槛,那就一无是处了。 传统上,一些游戏主机厂商(例如 Nintendo、Sony和 Microsoft)甚至对谁可以购买开发包具 有极大的限制。开发人员必须提出申请,并提供详细的公司信息以及计划开发的游戏的设计文 iPhone 游戏开发入门经典—— 也适用于 iPad 8 档。然后厂商再判断开发人员是否有足够的能力以及他们的游戏构思是否值得开发。如果您有 幸成为这些平台的授权开发人员,那么您还得支付开发包、授权、测试以及认证费用——总计 数千美元。 尽管有人会抱怨 iPhone 的 App Store 仍然是“带围墙的花园”,不过这些限制比起过去的专 用平台根本不算什么。Apple 要求的只是开发人员需要支付很小的一笔年费。Apple 不管您有多 少雇员,您的资金状况如何,以及您想要开发什么类型的游戏。任何人——不论是经验丰富的 团队还是刚刚学习游戏开发的学生——都可以开发游戏,而且和 Electronic Art、Sega 和 Gameloft 这些大开发商开发的游戏平起平坐。由于采用了数字化发行,因此所有的开发人员都 可以免费地将产品拿出来卖,而且任何开发人员的游戏都有机会作为特色游戏放在 App Store 的排行榜上(如图 1-7 所示)。 图 1-7 独立的小开发商开发的游戏和已有的大开发商和发布者开发的游戏直接竞争 为 iPhone 开发的费用也便宜得多。如果您还没有一台 Intel 的 Mac 计算机,计算机、设备 和开发人员费用可以控制在一千美元之内。而在其他大部分平台上,开发包本身的费用都会达 到这么多甚至更多,而且还没加上测试和认证费用。 和传统平台不一样,Apple 对开发过程本身完全不干涉。Apple 不要求设计文档,也不会设 置里程碑。Apple 唯一对您的游戏做要求的时候就是您的游戏完成并提交到 App Store 的时候。 iPhone 是第一个向所有开发人员开放的游戏平台。 1.8 创新——来自小开发商的大创意 开放平台往往伴随着创新。大量和各类 iPhone 开发人员带来的是前所未有的创新。iPhone 是独立的游戏开发人员向全世界展示他们作品的一种最佳新方式。在传统平台上无人问津的新 第 1 章 革命性的游戏平台:随时随地,人人都可以游戏 9 游戏在 App Store 上就不会面临这类困难。 像 Steph Thirion 开发的 Eliss 和 Mobigame 开发的 Edge(见图 1-8)这类独特且创新的游戏在 iPhone 上比其他任何平台都能更容易地进入市场且更快地造成影响。如果没有名气的开发人员 开发的游戏能够吸引大量爱好者,而且这些爱好者愿意口口相传,或者他们的游戏被 Apple 选 为 iTunes 的特色游戏,或者出现在电视广告上,那么这些开发人员有可能一夜成名。App Store 是独立开发人员可以和大开发商竞争甚至超越他们的其中唯一一个平台。 图 1-8 Eliss 和 Edge 是 App Store 上两款独特且创新的成功游戏 如果您有梦想,您就可以追逐梦想,并将作品发布出来。尽管对于 iPhone 有大量可用的应 用程序,它们会降低高质量游戏的信噪比,但是消费者和 Apple 一直在致力于寻找区分良莠不 齐的应用程序的方法。 1.9 本章小结 iPhone 具有无处不在、吸引力广泛、用户界面独特、连接性、超强的性能,以及低门槛等 特色,因此 iPhone 正在成为一个新的、革命性的、变化的移动游戏平台。独创的交互式体验(例 如增强的现实感)正在征服人们的双手。尽管 iPhone 和 App Store 正处在初步发展阶段,但是已 经在改写便携式游戏的规则。对于移动游戏开发人员,这是一个激动人心的时刻。第 2 章将详 细介绍开始 iPhone 游戏开发要使用的工具和技术。 iPhone 游戏开发:iPhone 工具箱一览 既然我们已经建立了对 iPhone 平台的信任,并且了解了为什么应该会对 iPhone 开发感到 兴奋,那么我们现在应该了解一下 iPhone 开发需要的一些工具。iPhone 开发的技术包括 Objective-C 或 C/C++、Xcode、UIKit、Quartz 2D、Core Animation、OpenGL、音频 API、网络, 以及 GameKit。本章简要介绍这些技术,并介绍如何应用这些技术来开发游戏,还介绍了在现 有的游戏中采用这些技术的示例。 2.1 开发工具和开发环境 iPhone 开发采用的语言是 Objective-C。顾名思义,Objective-C 是 ANSI(American National Standards Institute,美国国家标准协会) C 语言的扩展,为 C 提供了简单且直接的面向对象能力。 尽管大部分 iPhone API 都有 Objective-C 接口,但是应用程序的非接口部分可以通过 C/C++语 言编写,因为 Objective-C 语法是 GNU C/C++语法的超集。您至少需要了解一些 Objective-C 语 言,并具有 C/C++的相关经验。 对于我们,幸运的是 Apple 引以为自豪的是向开发人员提供了高质量的软件。在很长一段 时间内,这些工具为创建一流的 Mac 应用软件提供了可能,而 iPhone 开发使用的也几乎是这 一套工具。iPhone 开发的基础工具是 Xcode,Xcode 集成了界面设计、代码编辑、调试和性能 分析等功能。所有这些软件都免费提供,而且可以运行在任何基于 Intel 的 Mac 计算机上。 Xcode 集成开发环境(Integrated Development Environment,IDE)是一个功能齐全的代码编辑 器、项目管理器,以及图形调试器。Xcode 具有现代 IDE 的所有方便性,包括健壮的代码着色、 错误报告、代码完成,以及代码折叠功能。只要单击就可以完成应用程序的编译、安装和启动, 设备级的调试有助于捕捉 bug。请熟悉 Xcode 的用户界面和快捷方式,因为您将要在 Xcode 中 花很多时间编写 C/C++和 Objective-C 代码。 第 章 2 iPhone 游戏开发入门经典—— 也适用于 iPad 12 一旦游戏编写好并运行起来了,就可以使用 iPhone 模拟器了。模拟器可以模拟 iOS 除了加 速度传感器之外的几乎所有功能,因此是测试应用程序的更改的便捷的方式。但是要确保始终 在真实设备上测试了应用程序,因为模拟器不能反映设备的 CPU 性能和内存条件。 开发包内还有一些其他的工具来辅助 iPhone 应用程序的设计和优化。Interface Builder 提供 了一个图形用户界面编辑器,后者可以自动化 UIKit 元素的加载和定位,例如按钮和标签。如 果没有通过 OpenGL 来构建游戏,那么可以通过 Interface Builder 极大地简化菜单和其他静态元 素等项的创建。一旦到达开发的优化阶段,Instruments 就是一个非常顺手的工具。Instruments 是一个强大的性能分析工具,对磁盘、内存和 CPU 的使用情况等数据进行采集和可视化,允 许快速查找游戏中的瓶颈和内存占用。 2.2 UIKit 绘制图像和其他有用的 UI 元素最方便的方法之一是 UIKit。通过 UIKit 可以很方便地显示 和定位位图,而且由于底层硬件的加速,速度还比较快。对于那些没有大量图形元素或动画, 也不需要运行在最高 60fps 的游戏,UIKit 是一个极好的选择。除了绘制位图外,开发人员还能 方便地通过 UIKit 添加其他对游戏有用的 UI 元素,例如警告框、文本标签和文本输入框。通过 UIKit 还可以获得用户输入,例如,屏幕触摸和加速度传感器读数。 NimbleBit 开发的 Sky Burger(如图 2-1 所示)是一个倾斜控制的堆叠汉堡游戏,这个游戏完 全使用 UIKit 开发,没有直接使用 OpenGL ES。尽管 Sky Burger 有很多图形和动画元素,但是 它还没有到达 UIKit 以可接受帧率绘图的极限。如果需要对这类游戏添加更多的图形效果,那 么应该使用 OpenGL ES 以确保游戏可以快速运行在所有设备上。 NimbleBit 开发的 Textropolis(如图 2-1 所示)是另一个不需要 OpenGL ES 提供的强大图形渲 染能力的游戏示例。由于 Textropolis 是一个仅有少量背景动画的单词游戏,因此 UIKit 非常适 合这个游戏的开发。 图 2-1 NimbleBit 开发的 Sky Burger 和 Textropolis 是采用 UIKit 开发的简单 2D 游戏示例 第 2 章 iPhone 游戏开发:iPhone 工具箱一览 13 在第 3 章中,iPhone 开发人员 PJ Cabrera 详细深入地讲解了 UIKit,包括启动和运行 UIKit 需要了解的所有知识。如果您计划开发不需要达到 60fps 运行的简单 2D 游戏,那么可以考虑 通过 UIKit 来构建游戏。 2.3 Quartz 2D 和 Core Animation Quartz 2D(也称为 Core Graphics)向开发人员提供了更高级的底层绘图引擎。尽管 Quartz 2D 仍然是 2D 技术,但是它提供了一些有用且强大的功能,例如离屏渲染、透明层,以及高级 直线绘制。很多游戏开发人员可以方便地使用这些功能来绘制例如雷达屏幕、迷你地图、曲线 路径和其他 UI 元素等。尽管 Quartz 2D 对于游戏中主要元素的呈现可能太慢了,但是 Quartz 2D 也是 iPhone 开发人员工具箱中的另一个重要工具。 在第 4 章中,PJ Cabrera 会详细介绍 Quartz 2D 的细节和使用场景。如果您打算在游戏中进 行离屏渲染或进行高级绘制,就一定要读该章。 游戏怎么没有动画呢?Core Animation 向开发人员提供了一种轻松移动和转换 UIKit 元素 的方法。通过 Core Animation,可以创建硬件加速的、基于时间轴的动画,可移动化的属性包 括位置、旋转、透明、甚至矩阵变换。Core Animation 非常灵活,可以完成从动画游戏元素到 向游戏菜单和 UI 元素添加修饰的任何任务。Core Animation 还可以用于转换视图,提供了很多 内置的转换效果可以随时使用。 在第 5 章中,PJ 介绍了 Core Animation 的基础知识,甚至展示了如通过 Core Animation 来 创建类似粒子的效果。请不要跳过这一章,因为几乎所有的游戏都会使用到 Core Animation。 2.4 OpenGL ES 取决于打算开发的游戏的类型,OpenGL ES 可能会是工具箱中最为重要的工具。OpenGL ES 通过一个可以经实践证明的接口提供了强大的图形渲染能力(如图 2-2 所示)。不论游戏以二维 还是三维的方式绘图,通过 OpenGL ES 渲染图形都可以提供最快的选择。正是由于 iPhone 支 持这种渲染技术,因此 iPhone 可以竞争过其他主流的移动平台。由于大量开发人员都精通了 OpenGL,而且已有大量资源和代码,因此通过 iPhone 对 OpenGL 的支持,大量开发人员都可 以以熟悉的方式开发 iPhone 游戏。 OpenGL 使得以高帧率渲染 3D 图形成为可能,而且开发人员还在不停地致力于寻找将 iPhone 硬件性能发挥至极限的方法。 例如 OOO Gameprom 开发的 Real Racing 和 Wild West Pinball(如图 2-2 所示)游戏展示了 OpenGL 可以实现的 3D 图形效果。 Imangi Studios 开发的 Harbor Master 游戏(如图 2-2 所示)是休闲类游戏,也展示了通过 OpenGL 实现快速 2D 渲染的 能力。 OpenGL 专家 Ben Smith 在第 6 章~第 8 章介绍了所有必要知识。iPhone 3GS 带来了更强劲 的 3D 加速性能,很快本书的相关章节一定会被熟读很多遍。 iPhone 游戏开发入门经典—— 也适用于 iPad 14 图 2-2 OpenGL ES 向 2D 和 3D 游戏提供了高速的图形渲染能力 2.5 音频 API 任何平台上吸引人的游戏都少不了声音。幸运的是,当 iPhone 开发人员在游戏中需要使用 声音时有多种选择。取决于对游戏中音频的控制需求,可以选择更高级的 API(例如 OpenAL), 也可以选择更简单的内置服务。 通过音频 API,可以实现流式音频,播放简短的声音,甚至模拟 3D 空间中的音频。可以 允许玩家选择他们自己的歌曲作为音轨,也可以自己提供音轨。有一些游戏,例如 TAITO 开 发的 Space Invaders Infinity Gene(如图 2-3 所示),甚至根据用户选择的歌曲自动生成游戏关卡。 另外一些游戏,例如 Secret Exit 开发的 Zen Bound(如图 2-3 所示),通过音轨让玩家沉浸在特 定的心境中玩游戏,甚至鼓励用户使用耳机来获得完美体验。 图 2-3 Space Invaders Infinity Gene 通过玩家选择的歌曲来生成关卡。 Zen Bound 通过优美舒缓的音轨使得玩家沉浸在游戏中 第 2 章 iPhone 游戏开发:iPhone 工具箱一览 15 在第 9 章~第 12 章中,音频大师 Eric Wing 将描述 iPhone 音频的所有细节。介绍更基础的 音频 API 之后,Eric 将详细介绍 OpenAL,包括基本的音频播放、音频定位和流式音频。请阅 读这些章节,为游戏添加美妙的声音! 2.6 网络 网络游戏非常适合 iPhone 这种联网的平台。不论是回合制游戏、实时游戏、世界游戏还是 本地游戏,添加网络多玩家功能和高分榜功能都会为游戏增色不少。尽管单机游戏永远都不会 消失,但是越来越多的玩家都在寻求联网功能,在游戏中得到更多社交体验。 iPhone 开发人员可以通过很多技术为游戏增添联网功能。通过服务器和客户端之间的套接 字和流或 GameKit 提供的蓝牙配对功能,可以实现实时的多玩家游戏。全球高分榜之类的功能 可以留住玩家。 如 Freeverse 开发的 Flick Fishing(如图 2-4 所示)之类的游戏提供了简单的高分榜之外的功 能。通过 Flick Fishing 的 Fish Net 功能,玩家可以创建一个其他人可以加入的 Net,将那个玩 家的连续捉鱼加入 Net 中。通过这种小组高分榜机制,玩家可以和他们认识的人竞争,而不是 和陌生玩家竞争。通过 GameKit 的蓝牙配对功能, Firemint 开发的 Flight Control(如图 2-4 所 示)允许玩家和本地玩家协作游戏,方法是将自己的飞机降落在别人的设备中。 图 2-4 Flick Fishing 通过持久的小组高分榜将玩家联系在一起。 Flight Control 通过 GameKit 的蓝牙配对功能提供本地协作游戏 通过各种联网的方式,任何类型的玩家都可以在一起交互游戏。 在第 13 章~第 16 章中,网络高手 Peter Bakhirev 讲述了 iPhone 游戏中和联网相关的所有 细节。这些章节讲解了套接字、流、Bonjour、服务器、客户端、蓝牙和 Wi-Fi 连接。通过新学 习的网络知识,就可以让玩家获得更新更激动人心的体验。 iPhone 游戏开发入门经典—— 也适用于 iPad 16 2.7 本章小结 现在,您一定迫不及待地想要动手开始开发了。幸好,本页之后就开始全是真材实料了, 其中充满了源代码和真实的示例。现在 Ian Marsh 已经讲述了为什么要为 iPhone 开发游戏,并 简单介绍了所使用的工具。下面,就把本书交给 PJ Cabrera、Ben Smith、Eric Wing 和 Peter Bakhirev 等诸位高手。请学习下面章节中的所有信息,获得灵感,然后着手开发一些惊世之 作吧! 在小屏幕上移动图像 ——使用 UIKit 控件 现在您已经了解了 iPhone 和 iPhone 的游戏,并且了解了 iPhone 的开发环境。那么 iPhone 开发到底是怎样的呢?学习 Objective-C 有多复杂?第 2 章提到的那些工具有多好 用?如何开始开发 iPhone 游戏?本章将开启这些问题的解答之旅。 本章介绍 Cocoa Touch 应用程序开发环境、Objective-C 语言以及 UIKit 框架。本章讲 解了 iPhone SDK 中的两个密不可分的 IDE 和 GUI 设计工具:Xcode 和 Interface Builder。 本章通过 Xcode、Interface Builder 和 Cocoa Touch 构建了一款简单的 iPhone 游戏。这样下 来,您将熟悉必备的 API 以及一些重要的 iPhone 游戏设计技巧。 3.1 Cocoa Touch 简介 驱动 iPhone 的 Cocoa Touch 应用程序环境提供了各种 C 库和 Objective-C 框架,以及 相关联的专门用于构建 iPhone 应用程序的设计模式。本节介绍 iPhone 应用程序开发环境。 如果您已经熟悉了 Cocoa Touch 应用程序编程,那么可以跳过本节。 Cocoa Touch 环境由大量 Objective-C 框架和 C 语言库组成,开发人员可以通过这些访 问 iPhone 的 UI;通过 GPS 和指南针获得的地理位置信息;输入设备,例如触摸屏、加速 度传感器和照相机。开发环境中两个关键的框架为: ? Foundation 框架 定义了 Cocoa Touch 中常用的类,例如 NSObject 类和 NSString 类。 ? UIKit 框架 提供了创建 iPhone 界面所需元素的类,包括按钮、标签、文本框、滑块、 数据表以及其他 UI 组件。除了 UI 组件之外,UIKit 还定义了一些用于封装应用程 序对象的属性和方法的 Objective-C 类,以及定义不同应用程序、视图和输入事件的 原型(prototype)。 UIKit 还规定了开发人员使用这些类和原型应该采用的方法:通过推荐的经过时间考 验的面向对象程序设计(Object-Oriented Programming,OOP)结构对功能进行划分,例如模 型-视图-控制器(Model-View-Controller,MVC)设计模式和委托(Delegate)设计模式。本章将 讨论这些类、原型和设计模式。在讨论 UIKit 之前,首先快速学习一下 Objective-C 语言。 第 章 3 iPhone 游戏开发入门经典—— 也适用于 iPad 18 3.1.1 Objective-C 语言 Objective-C 是 20 世纪 80 年代初由 Brad Cox 设计的一种 OO(Object-Oriented,面向对 象的)语言。Objective-C 是 C 语言的一个超集,并且是围绕当时更为流行的 C++语言而独 立开发的一种语言。Brad Cox 在发明 Objective-C 语言时的目标是在 C 程序设计环境中引 入 Smalltalk 语言的语法和 OO 特性。他想在类似 Smalltalk 的环境和语言中工作。但是作 为一个多年的 C 程序员,他开发了很多 C 语言代码,而且依赖于那些他不想重写的 C 语言 代码。因此他发明了 Objective-C 将两种语言连接在一起。 为了更好地理解并使用 Objective-C 语言,需要了解并具有一些 C 程序设计的经验, 特别是要了解指针和内存管理。最好还要熟悉一些 OOP 的概念,例如类、类实例(或称为 对象)、继承、封装和多态性。如果您已经了解 Java、C#或 C++,学习 Objective-C 就简单 多了。对于调用方法 Objective-C 的语法和这些语言不同,而且 Objective-C 处理空对象、 未声明方法以及内存管理的方式和这些语言不同。 1. Objective-C 的简单示例 查看 iPhone 项目中的 Objective-C 代码时您可能首先注意到的是大部分代码文件都以.m 结尾(有时候也有.mm,在 C++和 Objective-C 混合编程的时候使用)。.m 表示模块(module)。 和 C 和 C++一样,Objective-C 的头文件以.h 为后缀。头文件用于声明类型和方法签名。 首先看一段简单的 Objective-C 示例代码。程序清单 3-1 展示了一个示例头文件,名为 SampleClass.h。 程序清单 3-1 示例头文件(SampleClass.h) #import @interface SampleClass : NSObject { float floatMember; } + (void)classMethod; + (int)classMethodWithParameter:(int)intParam; - (void)instanceMethod; - (float)instanceMethodWithParam1:(float)floatParam andParam2:(int)intParam; @end 程序清单 3-1 中的第一行代码是一条框架导入语句。在这里,导入了一个框架的头文 件。#import 与 C 和 C++语言的#include 语句类似。虽然 Objective-C 使用#import 而不使用 #include 的原因有好几个,但是其中一个原因是框架头文件的存放位置和标准 C/C++的 include 文件的存放位置不一致。另一个区别是#import 仅仅读取头文件一次。 程序清单 3-1 中的代码导入了 Foundation 框架。Foundation 框架的重要性在于这个框 架是其他所有 Objective-C 框架的基础。之所以称为 Foundation 框架是因为它是所有其他 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 19 Objective-C 框架的基础。 Foundation 框架定义了一些基类,在 iPhone 程序设计中将非常频繁地用到这些类。这 些类可以用来表示应用程序中典型的数据结构,例如数字、字符串、数组、集合,以及计 算机科学和面向对象中很多概念的实现。 下面是程序清单 3-1 中的第 2 行: @interface SampleClass : NSObject 这一行开始了类接口的声明。类声明自@interface 开始,至@end 结束。 在这里声明的类为 SampleClass。与 Java 和 C#不同,虽然类的名称不需要和文件名匹 配,但是最好能一致,这样可以为寻找代码提供方便。 在类接口声明中,类名后面有一个冒号,然后是这个类派生的类的名称。在这里, SampleClass 类由 NSObject 类派生而来。NSObject 是 Cocoa Touch 环境中最重要的类。Cocoa Touch 中所有的类都是从这个类派生而来的。 @interface 行之后有 3 行代码,只有组合在一起才有意义: { float floatMember; } 其中弯弯曲曲的符号称为花括号,在 C 语言和类似语言中通过花括号来组织代码。在 这里,为 SampleClass 类声明实例变量(instance variable)列表。在 C++中,这些变量称为成 员变量(member variable)。在 Java 中,这些变量称为属性(attribute)。在这段代码中只有一 个实例变量。这个实例变量的类型为 float,名称为 floatMember。 下面的 4 行代码由一些方法声明组成。 + (void)classMethod; + (int)classMethodWithParameter:(int)intParam; - (void)instanceMethod; - (float)instanceMethodWithParam1:(float)floatParam andParam2:(int)intParam; @end 这些和 C 语言和其他类似语言中的方法声明非常类似,只是参数列表没有放在圆括号 内,而且前面还有加号和减号。带有加号的方法声明为类方法(class method);带有减号的 声明表示实例方法(instance method)。也有包含参数的方法,但它们和 C 语言及其他类似语 言都不同。 在 Objective-C 中,方法的参数由方法名中参数位置前面的一个冒号以及后面的参数的 类型和名称表示。因此,类方法“classMethodWithParameter:”有一个名为 intParam 的整 型参数。实例方法“instanceMethodWithParam1:andParam2:”有一个名为 floatParam 的浮点 参数以及一个名为 intParam 的整型参数。 SampleClass 的接口声明结束之后,在代码中通过@end 告诉编译器。通常,这时该文 件也结束了。 接口声明完成之后,下面就可以实现 SampleClass 了,如程序清单 3-2 所示。 iPhone 游戏开发入门经典—— 也适用于 iPad 20 程序清单 3-2 示例实现文件(SampleClass.m) #import "SampleClass.h" @implementation SampleClass static int intMember; + (void)classMethod { NSLog(@"inside classMethod"); } - (void)instanceMethod { NSLog(@"inside instanceMethod"); } + (int)classMethodWithParameter:(int)intParam { intMember = intParam; return intParam; } - (float)instanceMethodWithParam1:(float)floatParam andParam2:(int)intParam { floatMember = floatParam; intMember = intParam; return floatParam * intParam; } @end 程序清单 3-2 的第一行导入了 SampleClass.h 文件。这样就可以在实现文件中使用 SampleClass 接口了。这一步对于 C 和 C++开发人员应该不陌生,因为这种方式和使用 #include 包含一个 C++类或 C 模块的一个头文件差不多。 下一行代码开始 SampleClass 的实现块: @implementation SampleClass 和@interface 块类似,该实现以@implementation 符号开始,接着是类的名称,最后以 @end 结尾。在类的实现块中,需要定义在接口中声明的方法。接口只具有类的方法的名 称和参数信息。实现部分具有这些方法以及完成这个类的实现需要的其他任何内部方法的 代码。 紧跟在@implementation 符号后面的下一行代码是一个类型为 static int 名为 intMember 的模块变量: static int intMember; 由于 Objective-C 并没有类变量,这个变量下面的使用方法和类变量类似。这实际上只 是一个普通 C 模块作用域内的变量。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 21 下面是对第一个方法的定义。 + (void)classMethod { NSLog(@"inside classMethod"); } 这个类中定义的第一个方法名为 classMethod,这个方法不接受任何参数也不返回任何 值。除了行首多了一个加号且后面没有圆括号(用于包含参数)之外,这个定义几乎和 C 语 言的方法定义一致。 和 C 语言和其他类似语言一样,方法体包含在一对花括号内。这个方法调用了一个 C 函数 NSLog,这个函数由 Foundation 框架提供。NSLog 能写入控制台日志,并且有助于调 试。NSLog 接受一个 Objective-C 字符串对象作为其参数。从代码中可以看出,Objective-C 字符串常量以@符号开始,从而和 C 字符串区分开来,C 字符串完全是另外一种结构。 Objective-C 字符串是 Objective-C 类 NSString 的对象,而 C 字符串只是字符数组而已。 下一个方法定义是一个实例方法(名为 instanceMethod): - (void)instanceMethod { NSLog(@"inside instanceMethod"); } 从行首的减号可以确定它是一个实例方法。和前面定义的类方法一样,这个方法也调 用了 NSLog,并且作为参数向 NSLog 传递了一个字符串对象。 下一个方法定义是名为“classMethodWithParameter:”的一个类方法: + (int)classMethodWithParameter:(int)intParam { intMember = intParam; return intParam; } 这个方法接受一个参数,即名为 intParam 的一个整数,并返回一个整数。在方法体中, intParam 的值被赋予伪类变量 intMember,然后它返回 intParam 的值。除了方法定义的第 一行,其他完全是纯 C 代码。 下一个方法定义是名为“instanceMethodWithParam1:andParam2:”的一个实例方法: - (float)instanceMethodWithParam1:(float)floatParam andParam2:(int)intParam { floatMember = floatParam; intMember = intParam; return floatParam * intParam; } @end 这个方法接受两个参数并返回一个浮点数。第一个参数是一个名为 floatParam 的浮点数。 第二个参数是一个名为 intParam 的整数。这段代码将 floatParam 的值赋予实例变量 iPhone 游戏开发入门经典—— 也适用于 iPad 22 floatMember,并将 intParam 的值赋予 intMember。然后它将 floatParam 和 intParam 相乘并 返回乘积结果。 最后一个方法定义结束之后,实现块以@end 符号结尾,同时 SampleClass.m 文件也结 束了。 这一节学习了如何声明接口与如何定义 Objective-C 类的实现。还学习了如何在接口中 声明方法的签名,以及如何定义这些方法中的代码。然后学习了如何使用 Objective-C 中方 法的参数。下面几节将学习如何使用 SampleClass 这个类。 2. 使用 Objective-C 类 下面看看如何分配并初始化一个实例,如何调用类方法和实例方法,以及如何调用带 参数的方法。程序清单 3-3 展示了相关的示例代码。 程序清单 3-3 下面的 Objective-C 代码展示如何分配并初始化对象,以及各种方法的 调用方式和参数传递方式 SampleClass *instance; instance = [[SampleClass alloc] init]; [SampleClass classMethod]; [instance instanceMethod]; int result1 = [SampleClass classMethodWithParameter: 5]; float result2 = [instance instanceMethodWithParam1: 5.0 andParam2: 2]; [instance release]; 程序清单 3-3 中的第一行代码声明了一个名为 instance 的变量,这个变量属于指向类 类型 SampleClass 的对象的指针类型。C 和 C++程序员应该非常熟悉这种语法,除了指针 符号(*)之外,C#和 Java 开发人员也应该觉得不陌生。 请看下一行代码: instance = [[SampleClass alloc] init]; 如果您对计算机语言有一些了解,就应该可以看出来这一行的开始将某些内容存储在 变量实例中。那么等号右侧是什么呢?那一堆方括号又是什么?这种语法看上去当然会很 奇怪,因为在 C 和其他类似的语言中,方括号的作用都是声明数组以及寻址数组中的元素。 但是这里方括号看上去不一样。在 Objective-C 中,方括号用于调用方法。这也是区分 Objective-C 方法调用和 C 函数调用的一种方式。 方法调用总是由两部分组成:第一部分是一个实例变量或一个类名,第二部分是一个 方法名和任何参数。 这里的方括号是嵌套的,也就是说一对方括号在另一对方括号之内。这有点像在很长 的数学表达式中的括号嵌套。在学校中我们学到:应该从最内层的方括号开始,从内向外 进行操作。在 Objective-C 中也一样,从最内层的方括号开始。 最内层方括号内的代码为 SampleClass alloc。只要看到方法调用的第一部分为类名, 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 23 就可以判断这个调用为类方法的调用。在这里,调用了 SampleClass 类的 alloc 方法。 另外,在 SampleClass 的接口声明中,并没有名为 alloc 的方法。这没错,因为 alloc 是 NSObject 类的方法。由于所有类都从 NSObject 类派生而来,因此所有派生而来的类都有 一个名为 alloc 的方法。alloc 方法创建一个类的实例,并且返回一个指向那个实例的指针。 因为这里的方括号是嵌套的,所以[SampleClass alloc]返回的结果作为第二对括号的第一部 分。因此,[SampleClass alloc]返回的实例用来调用一个名为 init 的实例方法。SampleClass 并没 有声明一个名为 init 的方法,因此,这实际上还在处理定义在 NSObject 类内的方法——在 这里为 NSObject 的实例方法,名为 init。 这个 init 方法用于将类的实例变量初始化为默认(或初始)值。通常,当一个类有很多 实例变量需要初始化为安全或有意义的默认值时,都需要重写 init 方法。除了覆盖 init 方 法之外,类还可以定义一些带有一个或多个参数的 init 方法,这样就可以在一个单独的方 法调用内为多个实例变量设置初始值了。 下面 4 行代码应该很浅显易懂。 [SampleClass classMethod]; [instance instanceMethod]; int result1 = [SampleClass classMethodWithParameter: 5]; float result2 = [instance instanceMethodWithParam1: 5.0 andParam2: 2]; [instance release]; 第一行代码调用了 classMethod 方法,后者不接受任何参数。下一行是对实例方法 instance-Method 的调用,该方法也不接受任何参数。接下来是对一个名为“classMethodWith- Parameter:”的方法的调用,这个方法要接受一个整型参数。在这里,向这个方法传递了整 数 2。这个方法返回一个整数值,这一行将这个整数值存储到一个名为 result1 的 int 变量。 最后,调用了一个名为“instanceMethodWithParam1:andParam2:”的方法,这个方法接受一 个浮点参数和一个整型参数。代码中向这个方法传递了浮点数 0.5 和整数 2。这个方法返 回一个浮点值,然后将这个值存储到浮点变量 result2 中。 程序清单 3-3 中的最后一个方法调用为[instance release],这是对另一个从 NSObject 类 继承而来的方法的实例方法调用。无论何时使用完自己通过 alloc 创建出来的一个对象实例 之后,都要调用 release 方法。在这个示例中,由于分配了一个实例,并且使用完了这个实 例,因此最后要释放它。 3. Objective-C 的内存管理 Objective-C 中的内存管理与 C 语言和其他类似语言不同。在 C 和 C++中,通过 malloc 或 new 分配内存,把它保存在一个指针中,然后将指针传递给其他需要的代码,在使用完 成之后释放/删除它。在 C#和 Java 以及一些脚本语言中,只要分配对象即可;对应的语言 运行时会在不引用对象的时候负责将对象释放。 在 Objective-C 中,release 和 C 语言中的 free 不一样。调用 release 会减少对象内部的 引用计数器。当内部引用计数到达 0 的时候,Objective-C 运行时就会将这个对象占有的内 存设置为无效。 首次通过 alloc 分配 Objective-C 对象的时候,把对象的引用计数设置为 1。在编写代 iPhone 游戏开发入门经典—— 也适用于 iPad 24 码的时候,需要自己负责管理引用计数,并且记住在合适的时候调用 release,否则该对象 就会产生内存泄漏。当像程序清单 3-3 那样创建一个短期使用的类实例时,很容易记住什 么时候该 release 对象。但是当长时间使用某些对象时,而且还被代码中不同的部分在不同 时刻使用的时候,事情就会变得很复杂。 NSObject 还有一个方法称为 clone,这个方法可以创建新对象,作为另一个对象的一 个副本。这个 clone 有自己的引用计数并把它设置为 1,而不管这个 clone 来自的那个原始 对象的引用计数为多少。和其他任何类型的 Objective-C 对象一样,使用完它们之后也要在 任何 clone 上调用 release。 在使用 Cocoa Touch 编程时,有些情况下不用自己分配对象。Cocoa Touch 框架有很多 方便的方法会分配对象并将对象返回。这些对象保存在自动释放池(autorelease pool)中。如 果您一直拥有 Cocoa Touch 框架返回的对象,Cocoa Touch 可能就会自动释放这个对象,并 且在您使用完它之前将其设置为无效,从而导致崩溃。在这种情况下,需要递增这个对象 的引用计数,这样才不会导致对象被设置为无效。这种操作称为保留(retain)对象。这个操 作通过调用对象上的 retain 方法实现。retain 方法是 NSObject 类的另一个实例方法, NSObject 由所有的 Objective-C 类继承。当保留一个对象的时候,依然要记得使用完它之 后要释放这个对象,这样 Cocoa Touch 框架才能自动释放并将这个对象设置为无效。否则 就会产生内存泄漏。 通过创建自动释放池可以在自己的代码中使用这种自动的对象清理机制。通过自动释 放池,可以以一种有组织且可靠的方式控制内存中对象的释放。要创建自动释放池,首先 分配 NSAutoreleasePool 类的一个实例并初始化它。由于 NSAutoreleasePool 实例和任何其 他 Objective-C 实例一样,因此在使用完这个实例时必须释放它。程序清单 3-4 展示了创建 和释放自动释放池的一个示例。 程序清单 3-4 创建和释放自动释放池 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // 其他代码放置在这里 [pool release]; 通过调用对象上的 autorelease 方法就可以将对象放在自动释放池内。autorelease 方法 是 NSObject 的另一个实例方法。在对象上调用 autorelease 可以将对象放进最近创建的那个 自动释放池内。当自动释放池被释放的时候,在这个自动释放池中添加的所有对象都会被 释放。 现在您已经学习了 Objective-C 的语法,还了解了一些关于 Objective-C 和 Cocoa Touch 内存管理的工作方式,下面准备深入介绍 Cocoa Touch 环境和 UIKit 框架的细节。 注意: 有关 Objective-C 的语法和内存管理更详细的内容,可以参阅 Mark Dalrymple 和 Scott Knaster 合著的 Learn Objective-C on the Mac 一书(由 Apress 出版社 2009 年出版)。Apple 还在 iPhone Dev Center(http://developer.apple.com/iphone)上提供了 Objective-C 的教程。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 25 3.1.2 Cocoa Touch 和 UIKit 框架 UIKit 框架的主要作用就是创建应用程序的界面。UIKit 框架定义了用于创建 iPhone 应用程序所需的各种组件的 Objective-C 类。UIKit 的类用于:iPhone 屏幕、视图、滚动视 图、表、表单元格、文本标签、按钮、图像,以及开发人员可以用来创建漂亮的 iPhone 应 用程序的许许多多控件。 应用程序的界面实际上通过布局 UIKit 类的实例创建,要么通过编程方式使用代码, 要么通过 Interface Builder 设计工具以图形的方式进行。垂直握住 iPhone 和 iPod touch 时, 屏幕宽为 320 像素,高为 480 像素,这种模式也称为纵向(portrait orientation)。作为一个游 戏开发人员,您的工作就是通过激动人心的动画和其他图形来填满屏幕以娱乐玩家。通过 UIKit 编程的时候,可以通过在 UIView 中放置图形对象来完成该任务。 1. UIView 简介 UIView 是一个容器对象。也就是说 UIView 可以包含按钮、图标、标签、文本框,以 及其他任何类型的 UI 组件。甚至还可以将 UIView 实例置于另一个 UIView 中。本章主要 关注的是在 UIView 内放置图像视图,并且根据游戏的逻辑移动这些图像。图像视图是一 类保存图像的 UIView。 UIView 是一个用来表示图形实际区域的 Objective-C 类。UIView 包含一些属性,例如 位置、大小、背景色、可见性,以及它是否响应触摸事件。了解这些 UIView 属性非常有 用,不仅因为需要在 iPhone 编程中使用 UIView,还因为所有其他的 iPhone 组件都是从 UIView 派生而来的。按钮、标签、文本框、文本视图和表实际上都是某种 UIView。也就 是说这些类都有这些属性,而且设置这些属性产生的行为都是一致的。 1) frame 属性 UIView 的屏幕位置和大小通过 frame 属性表示。frame 属性是一个类型为 CGRect 的 C 结构体。这个结构体表示一个矩形的详细属性。整个 iPhone UI 开发处处都会使用 CGRect 来指定标签、按钮和文本框等 UI 元素的大 小和位置。 CGRect 结构体由两个元素组成。 ? Origin:第一个元素是类型为 CGPoint 的 C 结构体,这个结构体保存的是矩 形左上角的 x 和 y 坐标。 ? Size:该元素是另外一个 C 结构体, 类型为 CGSize。它保存矩形的宽度 和高度。 图 3-1 展示了 UIView 中窗体的概念。 iPhone 屏幕左上角的点表示位置(0,0)。x 轴 从这一点向屏幕右侧延伸,因此 x 值为 319 表示屏幕的右侧边缘。y 轴从这一点向屏幕 下侧延伸,因此 y 值为 479 表示屏幕的底边 40 像素高 100 像素宽 图 3-1 UIView 的 frame 属性保存了视图的位置和大小 iPhone 游戏开发入门经典—— 也适用于 iPad 26 缘。因此屏幕右下角的点的位置为(319,479)。图 3-1 中矩形表示的视图的左上角的坐标为 (100,100),也就是说在屏幕左上角右侧 100 像素,下侧 100 像素。这一点是窗体的原点。 这个视图矩阵的宽为 100 像素,高为 40 像素,这就是这个窗体的大小。 下面的代码段创建了如图 3-1 所示的位置和大小的一个视图: CGRect newframe; newFrame.origin = CGPointMake(100,100); newFrame.size = CGSizeMake(100,40); UIView *myView = [[UIView alloc] initWithFrame:newFrame]; 注意: 您也许注意到了 CGRect、CGPoint 和 CGSize 的名称都以字母 CG 开头。它们都是 C 数据 结构体,而不是 Objective-C 类。CG 表示 Core Graphics。Core Graphics(也被称为 Quartz 2D)是 UIKit 内部用来绘制直线、曲线、渐变和图像的一个低级 C 语言库。第 4 章详细学习 Quartz 2D。 CGPoint 结构体的 x 和 y 元素,以及 CGSize 结构体的 height 和 width 元素都是 C 语言 的浮点值。也就是说,视图的任何位置坐标或视图的任何给定宽度或高度都可以设置为小 数值,例如 100.25。 iPhone的屏幕实际上由320乘以480个物理像素组成,那为什么还能指定部分像素呢? iPhone 和 iPod touch 有一种称为次像素渲染(subpixel rendering)的功能。当给定的位置和大 小值为小数的时候,iPhone 会通过在相邻像素之间插值来渲染图形,这样看上去好像部分 图形绘制在像素之间。 2) 背景色和 alpha 属性 UIView 的另外两个属性是背景色和 alpha。alpha(也称为不透明度(opacity))是透明度的 反义词。这个属性是一个无符号的浮点数,它接受 0.0(完全透明)~1.0(完全不透明)之间的 值。半透明的 alpha 值为 0.5。任何大于 1.0 的值都解释为 1.0。 设置视图的 alpha 属性的时候,放置在这个视图内的所有元素都会受其影响。因此, 如果一个视图中有一个按钮和一个标签,那么设置了这个视图的 alpha 为部分透明之后, 这些元素也会以部分透明的形式出现。 背景色是 UIColor 类型的属性。UIColor 是一个 Objective-C 类,它属于 UIKit 框架的 一部分。UIColor 包含一个颜色常量集用来表示常用的颜色。在简单的颜色选择场合下, 可以使用其中一种内置颜色常量以简化编程。下面一行代码将视图的背景色设置为红色: myView.backgroundColor = [UIColor redColor]; 还可以通过红、绿、蓝和 alpha 值的特定强度来指定颜色。 myView.backgroundColor = [UIColor colorWithRed:1.0 green:0.5 blue:1.0 alpha:1.0]; 对于游戏,大部分情况下视图都应该是完全透明的——也就是说能看到背面的内容。 您不能让视图阻碍视线。可以通过两种方式来实现这一点: ? 将 backgroundColor 属性的 alpha 值设置为 0。 ? 使用一个方便的颜色常量 clearColor,如下所示: 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 27 myView.backgroundColor = [UIColor clearColor]; 3) center 属性 UIView 的另一个属性是 center,通过 center 可以指定视图中心点的坐标。设置 center 属性的时候,view 会根据 frame 属性的 size 元素重置该窗体的 origin 元素。 myView.center = CGPointMake(15,25); UIView 中还有很多其他属性在游戏编程中很有用。通过示例可以学习这些属性。 下面来创建第一款游戏,纸上谈兵就这么多了。 3.2 构建一款简单的游戏 下面我们要构建一款游戏,游戏中有一个小球在屏幕中到处弹跳,当小球碰到彩色砖 块时,砖块就会消失。首先简单介绍一下如何创建新的 Xcode 项目与如何使用 Interface Builder。如果您已经熟悉使用 Xcode 和 Interface Builder,那么可以直接跳到 3.2.3 小节。 3.2.1 创建一个 Xcode 项目 要使用 UIKit 创建一款 iPhone 游戏,应该在 Xcode 中选择创建新的 View-based Application 项目。打开 Xcode,选择 File | New Project 命令,打开 New Project 对话框。在 左侧列表中的 iPhone OS 下面选择 Application,然后在右侧的面板中选择 View-based Application 模板,如图 3-2 所示。然后单击 Choose 按钮。 图 3-2 在 New Project 对话框中选择 View-based Application 模板 接下来,Xcode 会提示项目的名称和保存它的位置。因为我们要创建一个击中砖块的 iPhone 游戏开发入门经典—— 也适用于 iPad 28 游戏,所以输入名称 IVBricker,然后把它保存在一个喜欢的位置(作者 PJ Cabrera 把它保 存在当前用户的 Desktop 文件夹下)。Xcode 在指定的地方创建一个名为 IVBricker 的文件 夹,然后创建该项目,如图 3-3 所示。 图 3-3 在 Xcode 中加载的 View-based Application 一旦在 Xcode 中加载项目,就在左侧会看到一个名为 Groups & Files 的面板(在工具栏 之下)。图 3-3 中突出显示的 IVBricker 图标表示您的项目。选择了这个图标之后,在右上 角的 Detail 面板内可看到用于构建应用程序的所有文件和框架。在左侧面板的 IVBricker 图标之下,可以看到以下文件夹图标。 ? Classes 和 Other Sources:这两组内包含了应用程序的源代码。Classes 组是默认集 合中唯一反映磁盘上文件夹的一组。在这个示例中,在 Desktop 上该项目的文件夹 内有一个文件夹名为 Classes。应用程序的大部分源代码都在这个组内。 ? Resources:这一组包含应用程序所需运行的数据文件,例如图像和声音等。Xcode 在构建项目时会将这些数据文件和编译的代码打包在一起。当向应用程序添加新 资源的时候,可以将数据文件放在这一组内。除了数据文件之外,为了保证一致 性,作者 PJ Cabrera 将 Interface Builder 文件放在这一组内。 ? Frameworks:这一组包含了对代码中使用的不同的 Cocoa Touch 框架的引用。Xcode 在生成项目的时候会指定一些默认的框架,这些框架对于本章中的示例项目足够了。 ? Products:这一组包含了对构建的应用程序包(application bundle)的引用。这个应用 程序包就是在准备发布应用程序的时候向 App Store 提交的内容。 这些就是 Xcode 创建 app 项目的时候创建的 5 组标准文件。选择其中一组时,只有和 这一组相关的文件才会显示在 Detail 面板中。尽管这些组有文件夹图标,但是它们不一定 对应磁盘上的文件夹。这只是 Xcode 根据功能来组织文件的辅助方式。 既然您已经了解了 Xcode 组织项目的方式,下面看一下新的基于视图的应用程序的 UI。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 29 3.2.2 创建 IVBricker 用户界面 在 Xcode 中选择 Resources 组,可以看到 3 个文件: IVBricker-Info.plist、MainWindow.xib 和 IVBrickerViewController.xib。其中两个以.xib 结尾的文件是 Interface Builder 文件。 Interface Builder 是 iPhone SDK 自带的 GUI 设计工具。对于通过 UIKit 构建游戏,您 只需要了解 Interface Builder 的一些基本常识。下面首先看一下主窗口文件。 1. 查看主窗口文件 在 Xcode 中选择 MainWindow.xib 文件,在 Interface Builder 中双击它以打开该文件。 Interface Builder 窗口提供了 3 个视图,可以通过左上角的 View Mode 图标在 3 个视图之间 切换:图标视图模式、显示组件名称和类型的列表视图模式(如图 3-4 所示),以及层次结 构的视图模式。 图 3-4 MainWindow.xib 文件包含了构建应用程序所需要的 UI 组件 当一个 iPhone 应用程序启动的时候,Cocoa Touch 会在应用程序的 Info.plist 文件中查 找一个名为 Main nib file base name 的属性,然后加载这个属性指定的文件。在从 Xcode 模板创建的应用程序中,这一属性被硬编码为应用程序的 MainWindow.xib 文件。在加载 MainWindow.xib 文件的时候,Cocoa Touch 会处理在文件中设置的如下组件和属性。 ? File's Owner:每一个 Interface Builder 文件都有一个 File’s Owner 组件。当 Cocoa Touch 加载一个 Interface Builder 文件做的第一件事情就是创建一个在 File’s Owner 中指定类型的对象。在 MainWindow 的示例中,File's Owner 对象的类型为 UIApplication。这是一个很重要的 UIKit 类,这个类会为应用程序设置 Cocoa Touch 环境,并且在操作系统和其余应用程序之间传递事件。这是 iPhone 应用程序实例 化的第一个 UIKit 对象。 注意: 从游戏开发的角度考虑,我们通常可以忽略 Interface Builder 中的 First Responder 对象。 有关这个对象的详情可以参考 Dave Mark 和 Jeff LaMarche 撰著的 Beginning iPhone 3 Development(2009 年由 Apress 出版社出版)。 iPhone 游戏开发入门经典—— 也适用于 iPad 30 ? Application Delegate:每一个 UIApplication 示例都需要一个 Application Delegate 对象,否则这个应用程序就无法工作了。Cocoa Touch 加载 MainWindow 的时候, 会创建一个在应用程序委托中指定的类型的实例。一旦 Cocoa Touch 加载完 MainWindow 并处理完文件中指定的所有组件和属性的时候,Cocoa Touch 就调用 该应用程序委托,这样应用程序就知道 Cocoa Touch 已经完全启动。如果没有应用 程序委托,这个调用就无法完成,这样这个应用程序的组件只能被闲置。应用程 序委托的作用是接受应用程序的生命周期事件的消息。本章后面会详细讨论这些 应用程序委托消息。 ? Window:Application Delegate 对象创建完成之后,Cocoa Touch 会加载 Window 组 件。在 Cocoa Touch 中,Window 组件保存了组成一个应用程序的几个不同视图。 这个 Window 组件就是应用的主 UIWindow 实例。 ? View Controller:View Controller 对象表示应用程序中主视图的控制器。在这个示 例中,主视图控制器为 Bricker View Controller,类型为 IVBrickerViewController。 视图控制器类负责响应 Cocoa Touch 事件,Cocoa Touch 事件让控制器了解视图的 状态,例如视图已经被加载,或者 Cocoa Touch 准备卸载视图以回收内存。在本章 的示例游戏中,我们将要将这个类扩展为可以响应多点触摸、加速度传感器和其 他类型的事件。 综上所述,在应用程序启动的时候,Cocoa Touch 加载 MainWindow.xib 文件并处理这 个文件。在处理.xib 文件的过程中,Cocoa Touch 会创建 UIApplication 实例、应用程序委 托、主窗口、主视图,以及视图的视图控制器。然后通过不同组件的属性将它们连接在一 起。一旦完成该任务之后,Cocoa Touch 通知该应用程序委托该应用程序已经完成启动了。 现在我们已经结束对 MainWindow 文件的讨论,可以关闭该文件并返回到 Xcode。 2. 查看视图控制器文件 在 Xcode 项目中,双击 Resources 组中的 IVBrickerViewController.xib 文件,可以在 Interface Builder 中打开这个文件,如图 3-5 所示。 图 3-5 Interface Buildere 中的 IVBrickerViewController.xib 文件 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 31 IVBrickerViewController.xib 文件展示了组成应用程序的主视图的视图控制器的 Cocoa Touch 组件。其中包含视图本身,它通过称为 View的组件表示。还有 IVBrickerViewController.xib File’s Owner,这是 IVBrickerViewController 类型的对象。这个类型和 MainWindow.xib 中 的视图控制器的类型一致,这不是什么巧合,它们是同一对象。 这个 Interface Builder 文件中的 View 组件是放置构成我们的主游戏屏幕的不同 UI 组 件的地方,例如文本标签。 3. 设置 View 属性 在 Interface Builder 中双击 View 组件,打开一个窗口以显示视图的可视化布局。目前, 这个视图是空的,背景为暗灰色。 按 I 快捷键打开 Inspector 窗口。如图 3-6 所示,我们可以通过 Inspector 窗口来 更改视图的属性。 图 3-6 通过 Inspector 窗口(右侧的窗口)可以更改 UI 组件的属性。在 View 窗口(中间的窗口)中可以放置 UI 组件 Inspector 窗口顶部有 4 个页签。左边第一个页签称为 Attributes 页签,在这里可以修 改应用程序的 UI 组件的各项属性。 首先更改 Background 属性,将视图的背景色设置为其他颜色。首先确保选择了 Inspector 窗口中的 Attributes 页签,然后单击 Background 属性的色板,打开颜色挑选器。将视图的 背景色设置为黑色。更改完颜色后关闭颜色挑选器。 4. 向视图中添加组件 下面向视图中添加一些组件。通过 L 快捷键打开组件库。打开一个名为 Library 的窗口,窗口中显示了不同类型的组件,这些组件都可以布局在 Interface Builder 中的一个 视图。在 Library 窗口的顶部,选择分段按钮的 Object 部分,然后选择 Cocoa Touch 库中 的 Inputs & Values 组。 下面向视图中拖拽两个标签,用于显示游戏过程中的当前分数。从 Library 窗口中显 示的组件类型中选择 Label,然后把它拖动到 View 窗口中,将其放置在右上边缘附近。 iPhone 游戏开发入门经典—— 也适用于 iPad 32 注意: 在 View 窗口中移动 UI 组件的时候,注意 Interface Builder 中的蓝色虚线提示。这些 蓝色虚线会在视图中移动 UI 组件的时候出现或消失,这些虚线提示有助于将组件定位在 距离视图的边缘或其他组件合适距离的位置上。这些虚线的作用是提供帮助,在设计的时 候要使用它们。 选择新的标签,查看 Inspector 窗口中 Attribute 页签中显示的 Text 属性。这个属性为 Label 的当前值。将这个标签的 Text 属性更改为 5 个 0,即 00000。这个标签可以在游戏过 程中显示分数的变化。 Font Size 属性有一个名为 Adjust to Fit 的复选框,目前它处于选中状态。取消选择这 个复选框,让分数以固定字体大小显示。这样可以让标签截断我们设置的 5 个 0,因为这 个标签不够大,无法显示整个字符串。切换到 Inspector 的第 3 个页签,即带有一个尺子图 标的页签。这是 Size 页签。将 W(宽度)属性更改为 50。现在,这个标签的宽度为 50 像素, 可以很好地显示 5 个 0 组成的字符串。现在将 X 和 Y 属性分别修改为 275 和 10。另外, 在坐标的左侧有一个点网格。确保选择了中心点,使得网格中央有一个红色的小十字。在 本章中,x 和 y 坐标都表示组件的中心点。 把一个新标签拖动到分数标签的左侧。将 Text 属性更改为“Score:”。同样,取消选择 Font Size 属性中的 Adjust to Fit 复选框。然后切换到 Size 页签。将 W(宽度)属性更改为 50, 然后分别将 X 和 Y 属性更改为 216 和 10。如果还没有选择网格的中心点,请不要忘了选 择,否则标签的位置就不对了。 现在,我们的视图有了一个分数标签,看上去更像一个正在进行中的游戏。图 3-7 展 示了标签的位置。 图 3-7 标签组件及其在视图中的位置 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 33 添加完这些标签之后的下一步是什么呢?填满了 0 的分数标签是代码显示游戏的分数 的地方。在我们的游戏代码中,一旦分数变化,都要更新显示在分数标签中的值。要从游 戏代码中更新分数标签,首先需要在视图控制器中添加一个属性,这个新属性的名称为 scoreLabel。在视图控制器中添加完属性之后,需要在 Interface Builder 中在视图控制器中 的 scoreLabel 和分数标签组件之间建立一个连接。 继续之前,在 Interface Builder 中保存对 IVBrickerViewController.xib 所做的更改。 5. 向视图控制器中添加代码 返回 Xcode 项目,然后选择项目树中的 Classes 组。在右上方的 Detail 面板中,选择 IVBrickerViewController.h 文件。该文件的内容会显示在 Detail 面板下方的 Editor 面板中。 程序清单 3-5 显示了 IVBrickerViewController.h 文件的代码。以粗体显示的代码表示对 scoreLabel 实例变量(也称为 ivar)的声明及其对应的属性,另一个实例变量的作用是保存分 数。将这几行代码添加到文件中并保存所做更改。 程序清单 3-5 IVBrickerViewController.h 中的新代码,声明了 scoreLabel 实例变量 与同名属性,以及一个整型实例变量 score #import @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @end 下面对 IVBrickerViewController.m 文件做相应的修改。在 Editor 面板中打开这个文件, 添加程序清单 3-6 中所示的粗体代码。 程序清单 3-6 IVBrickerViewController.m 中的新代码合成 scoreLabel 属性,并在 dealloc 方法中释放它 #import "IVBrickerViewController.h" @implementation IVBrickerViewController @synthesize scoreLabel; - (void)dealloc { [scoreLabel release]; [super dealloc]; } 作者 PJ Cabrera 喜欢将 dealloc 方法定义从文件末尾移到最后一条@synthesize 语句之 iPhone 游戏开发入门经典—— 也适用于 iPad 34 后。为属性添加了@synthesize 语句之后,通常需要在 dealloc 方法中添加对应的 release 语 句。将 dealloc 移到@synthesize 语句之后可以省去很多代码的滚动,而且可以有助于提醒 在 dealloc 方法定义中为类的属性添加必要的 release 语句。 既然在类中添加了代码,就可以在 Interface Builder 中为属性和标签组件建立连接。在 继续之前确保已经保存了对 IVBrickerViewController.h 文件和 IVBrickerViewController.m 文 件所做的更改。 6. 连接属性和组件 切换至 Interface Builder,打开 IVBrickerViewController.xib 文件。右击 File’s Owner, 会打开一个窗口,其中列出了 File’s Owner 的 Outlets 和 Referencing Outlets。在 Outlets 列 表中第一个就能看到 scoreLabel。单击并按住 scoreLabel 右侧的空心圆,然后拖动到 Label(00000)组件中,如图 3-8 所示。 图 3-8 在 Interface Builder 中为 scoreLabel 属性和 Label 组件建立连接,使得在视图中能够显示分数 注意: 为标签连接属性的另一种方法为:选择 File’s Owner,在 Inspector 窗口中切换到左侧 第二个页签,即 Connections 页签。从 scoreLabel 的空心圆拖拽到相应的标签组件。 既然建立了连接,就可以在游戏中方便地更新分数标签了,视图控制器中的示例代 码如下: scoreLabel.text = [NSString stringWithFormat:@"%05d", score]; 做好了这些基本设置之后,下面就可以开始学习如何使用 UIKit 来专门开发 iPhone 游戏。 注意: 有关 Cocoa Touch、Xcode 和 Interface Builder 的详细信息,请参阅 Dave Mark 和 Jeff LaMarche 撰写的 Beginning iPhone 3 Development(2009 年由 Apress 出版社出版)。 3.2.3 华丽的图像才够格 我们正在开发的这个击砖块游戏需要有一个球在屏幕上弹跳。因此在开始实现这款游 戏之前,首先向示例项目中添加一个球。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 35 注意: 如果您因为已经了解 Objective-C 并知道如何使用 Xcode 和 Interface Builder 而跳到这 里了,您就可以下载源码并加载示例项目 IVBricker first stage。这样可以到达前一节结束 时的位置。 1. 添加图像 在针对本章下载的源代码中,有一个名为 IVBricker finished version 的文件夹。在这个 文件夹中可以找到一个名为 ball.png 的文件。将这个文件复制到当前的项目文件夹下。当 然,仅仅是将文件复制到项目文件夹下还不够。下一步,将这个文件从项目文件夹中拖到 Xcode 的 Resources 组中。在构建项目的时候,Xcode 会将这一组内的所有文件复制到应用 程序中。 将文件复制到项目的 Resources 组之后,可以将这个文件添加视图。返回 Interface Builder 打开 IVBrickerViewController.xib 文件。向视图添加两个标签之后,View 窗口应该 是打开的。打开 Interface Builder Library 窗口(按 L 快捷键)。在 Library 窗口中选择 Data Views 组。要显示图像,应该使用 Image View 组件。 图像视图是类型为 UIImageView 的 UIKit 组件,它们用于显示图像。iPhone SDK 支 持 JPG、GIF、PNG、TIFF、BMP、ICO(Windows 图标)和 CUR(Windows 光标)文件,以及 XBM 文件格式。UIImageView 类的对象有各种属性,如 height、width、父视图中的 x 和 y 坐标、图像透明度、图像拉伸比例,以及背景色等。 从 Library 窗口中拖一个图像视图到 View 窗口中。在 View 窗口中选择新的图像视图, 在 Inspector 窗口中的 Attributes 页签中寻找 Image 属性。在下拉列表中,可以看到 ball.png, 选择它作为图像,这时图像视图会显示球形,如图 3-9 所示。 图 3-9 图像视图正在显示球形。我们需要调整 Image View 组件的大小,使得这个组件能更好地适合小球的大小 在 Inspector 窗口中选择 Size 页签,将图像视图的宽和高设置为 16×16 像素,将 X 和 Y 分别设置为 159 和 239,小球会被放置在视图的正中间(不要忘了选择网格的中心点以显 示红色十字线,否则小球会定位错误)。 iPhone 游戏开发入门经典—— 也适用于 iPad 36 保存以上工作并返回 Xcode。构建并运行项目。在模拟器中启动应用程序的时候,应 该可以看到黑色的背景、红色的小球,以及显示的分数。但是这些元素只是固定在那里不 动。我们应该让小球动起来。 为了能够在游戏代码中以编程方式控制小球,应该在视图控制器中将小球添加为属 性,添加的方式和之前在视图控制器中添加 scoreLabel 的方式差不多。在 Xcode 中,打开 IVBrickerViewController.h 文件,然后添加程序清单 3-7 中加粗的代码,这些代码声明了 ball 实例变量以及同名属性。 程序清单 3-7 IVBrickerViewController.h 中的新代码,声明了 ball 实例变量以及同名属性 #import @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; UIImageView *ball; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @property (nonatomic, retain) IBOutlet UIImageView *ball; @end 下面对文件 IVBrickerViewController.m 进行修改。在 Editor 面板中打开这个文件,然 后添加程序清单 3-8 中加粗的代码,这些代码合成了 ball 属性并且在 dealloc 方法中释放它。 程序清单 3-8 IVBrickerViewController.m 文件中合成 ball 属性的代码和在 dealloc 方法中释放它的代码 #import "IVBrickerViewController.h" @implementation IVBrickerViewController @synthesize scoreLabel; @synthesize ball; - (void)dealloc { [scoreLabel release]; [ball release]; [super dealloc]; } 一旦在 IVBrickerViewController.h 和 IVBrickerViewController.m 中添加新代码之后,就 应该在 Interface Builder 中将图像视图连接至视图控制器中的属性上。连接的步骤和连接分 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 37 数标签和 scoreLabel 属性的步骤一致(在 3.2.2 节中描述过)。将 ball 属性连接到 ball 图像视 图之后,下面可以开始让小球在屏幕上活动起来。 2. 运动的假象 为了在屏幕上创造出运动的假象,电影和电视都通过短时间内显示快速连续的静态帧 来实现。这是术语帧/秒用在视频性能中的起源。在游戏编程中,理想的显示逻辑是每秒至 少 30 次,这样就能得到画面在屏幕上流畅移动的假象了。 在 iPhone 上,Foundation 框架提供了一个 NSTimer 类,通过这个类可以定时每秒重复 调用一个方法多次。下面编写一个方法通过 NSTimer 类来随着时间的推移变化修改小球的 位置,从而实现小球在屏幕上的弹跳。 注意: 在 iPhone SDK 3.1 中提供了一个新的方法来创建定时器,称为 CADisplayLink,这种 方法更适合于游戏。CADisplayLink 可以和 iPhone 显示同步,保证了每秒 60 次的刷新。虽 然本章后面会继续使用 NSTimer 作为游戏示例,但是最后会切换到使用 CADisplayLink 的 代码。 返回 Xcode,在 Editor 面板中打开 IVBrickerViewController.h 文件。添加程序清单 3-9 中加粗的代码。 程序清单 3-9 向视图控制器的接口中添加 ballMovement 实例变量 @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; UIImageView *ball; CGPoint ballMovement; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @property (nonatomic, retain) IBOutlet UIImageView *ball; - (void)initializeTimer; - (void)animateBall:(NSTimer *)theTimer; @end 这段代码向视图控制器中添加了另一个实例变量,称为 ballMovement,这个变量保存 小球每帧移动的像素数。CGPoint 是 Core Graphics 框架提供的 C 结构体。它拥有 X 和 Y 属性。 通过这两个属性可以指定不同的垂直和水平移动速率。 这段代码还声明了 initializeTimer 和“animateBall:”方法的签名。 iPhone 游戏开发入门经典—— 也适用于 iPad 38 下一步,打开 IVBrickerViewController.m 文件。移除 viewDidLoad 方法的注释,然后 应用程序清单 3-10 中粗体显示的更改。注意 initializeTimer 和“animateBall:”方法的代码。 程序清单 3-10 视图控制器中 viewDidLoad、initializeTimer 和“animateBall:”方法 的实现代码 - (void)viewDidLoad { [super viewDidLoad]; ballMovement = CGPointMake(4,4); [self initializeTimer]; } - (void)initializeTimer { float theInterval = 1.0/30.0; [NSTimer scheduledTimerWithTimeInterval:theInterval target:self selector:@selector(animateBall:) userInfo:nil repeats:YES]; } - (void)animateBall:(NSTimer *)theTimer { ball.center = CGPointMake(ball.center.x+ballMovement.x, ball.center.y+ballMovement.y); if(ball.center.x > 300 || ball.center.x < 20) ballMovement.x = -ballMovement.x; if(ball.center.y > 440 || ball.center.y < 40) ballMovement.y = -ballMovement.y; } 这段代码实现了视图控制器中的 viewDidLoad、initializeTimer 和“animateBall:”方法。 viewDidLoad 方法设置了 ballMovement 实例变量的初始值,即 4 像素高,4 像素宽。接下 来,它调用了 initializeTimer 方法。initializeTimer 方法创建了一个 NSTimer 实例,并将其 初始化为每秒调用“animateBall:”方法 30 次。 “animateBall:”方法首先获得球心的坐标,然后根据 ballMovement 坐标调整下一次 小球的位置。这种方法可以在每帧水平和垂直移动小球 4 像素。“animateBall:”方法剩下 来的代码检查小球是否接触到了屏幕的 4 个边缘。如果它碰到了,代码就将坐标的值从 4 改为-4,从而改变小球移动的方向,反之亦然。例如,如果小球从左向右移动,并且它碰 到了右边缘,那么小球从边缘“弹回”,并开始向左移动。 您可能注意到了,“animateBall:”方法中的代码并没有用到作为参数传入的 NSTimer 对象。本章后面会使用到这个对象。例如,当玩家“丢命”和游戏结束的时候,可以添加 代码将定时器停止。 保存之前的工作,现在构建并运行该项目,感受模拟器上 iPhone 2D 动画图片的奇妙。 现在已经实现了动画,下面添加一些用户交互。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 39 注意: 在这个简单的示例中,作者把游戏状态和游戏逻辑代码都放在视图控制器中。在设计大 型游戏的时候,游戏中会有很多敌人、障碍以及其他很多需要跟踪的游戏元素,因此更有组 织性的方法是创建多个类对游戏进行建模,并且将游戏逻辑代码和游戏模型代码分开。视图 控制器的任务变得更简单,只要将输入事件发送给游戏逻辑并且根据游戏模型更新视图即可。 将模型代码、视图代码和控制器代码分离的方式称为模型-视图-控制器(model-view-controller) 设计模式。 3. 接受用户输入,赋予游戏生命 iPhone 平台为游戏提供了非常高级的输入技术。除了触摸屏幕之外,iPhone 可以同时 跟踪多个触摸。通过在屏幕上同时感知多个触摸区域,可以创建出复杂的 UI。iPhone 平台 系列的所有设备都内置了一个加速度传感器,从而使应用程序可以跟踪移动状态,创造出 非常有趣的游戏体验。 下面向这个游戏的雏形中添加一块小木板,尝试在代码中添加输入处理。在本章的源 代码下载中,找到一个名为 IVBricker finished version 的文件夹,从中找到一个名为 paddle.png 的文件。将这个文件复制到项目文件夹下,然后将这个文件添加到项目的 Resources 组中,方法和前面添加 ball.png 文件的方法一样。 接下来,在 Interface Builder 中加载 IVBrickerViewController.xib 文件(如果它还没有打开), 从 Cocoa Touch 组件库中添加一个图像视图。将新图像视图的图像文件指定为 paddle.png。将 paddle 图像视图的宽和高分别设置为 60 和 16,x 和 y 坐标分别设置为 159 和 399。不要忘 记了选择定位网格中的中心点,以显示红色的十字,这样小木板才能正确地放置。 现在返回 Xcode。向视图控制器中添加另一个实例变量和属性,从而可以通过代码控 制小木板。指定实例变量和属性的名称为 paddle。程序清单 3-11 以粗体展示了视图控制器 的接口中代码的变化。 程序清单 3-11 向视图控制器的接口添加一个名为 paddle 的实例变量和属性 #import @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; UIImageView *ball; CGPoint ballMovement; UIImageView *paddle; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @property (nonatomic, retain) IBOutlet UIImageView *ball; iPhone 游戏开发入门经典—— 也适用于 iPad 40 @property (nonatomic, retain) IBOutlet UIImageView *paddle; - (void)initializeTimer; - (void)animateBall:(NSTimer *)theTimer; @end 程序清单 3-12 使用粗体列出了视图控制器的实现中的更改。 程序清单 3-12 在视图控制器的实现中合成与释放 paddle #import "IVBrickerViewController.h" @implementation IVBrickerViewController @synthesize scoreLabel; @synthesize ball; @synthesize paddle; - (void)dealloc { [scoreLabel release]; [ball release]; [paddle release]; [super dealloc]; } 既然在代码中完成相应的更改,就返回 Interface Builder,并将 paddle 属性连接到 paddle 图像视图上。完成之后,就可以通过代码来移动屏幕上的小木板了。 4. 处理加速度传感器输入 首先向代码中添加加速度传感器的控制代码,看一看这是怎么工作的。程序清单 3-13 列出了需要在示例项目中启用加速度传感器支持的代码。向 IVBrickerViewController.m 文 件添加加粗的代码。 程序清单 3-13 加粗的代码向视图控制器中添加加速度传感器支持 - (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)accel { float newX = paddle.center.x + (accel.x * 12); if(newX > 30 && newX < 290) paddle.center = CGPointMake( newX, paddle.center.y ); } - (void)viewDidLoad { 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 41 [super viewDidLoad]; UIAccelerometer *theAccel = [UIAccelerometer sharedAccelerometer]; theAccel.updateInterval = 1.0f / 30.0f; theAccel.delegate = self; ballMovement = CGPointMake(4,4); 程序清单 3-13 中的第一个方法通过读取加速度传感器的数据检测左右方向的运动,将 读数乘以 12,并加到小木板当前的位置上。只有小木板没有移过视图的左右边缘的时候才 会更新位置。 下一段新代码在 viewDidLoad 方法中,这段代码配置加速度传感器每秒读数 30 次。 这段代码还将视图控制器设置为加速度传感器委托。也就是说,加速度传感器会调用程序 清单一开始定义的“accelerometer:didAccelerate:”方法。 如果构建这段代码,编译器就会在设置加速度传感器委托的代码行中发出一个警告。 虽然代码的编译和运行都没有问题,但是编译器警告非常烦人。为了消除警告,打开 IVBrickerViewController.h 文件,更改下面这一行: @interface IVBrickerViewController : UIViewController { 改为: @interface IVBrickerViewController : UIViewController { 注意: 如果在模拟器上运行这个最新版的示例,就会发现这个程序其实没执行什么操作。因 为模拟器不支持读取加速度传感器的数据。需要将项目设置为在设备上运行以测试这段代 码。这需要注册为 iPhone 开发人人员并登录。有关如何完成的方式请参阅 Apple iPhone Developer Connection 网站(http://developer.apple.com/iphone)。 玩这个游戏的时候,首先将设备竖立起来,然后通过左右倾斜来移动小木板。倾斜的 角度越大,小木板移动的速度越快。 一旦在开发设备上运行这段代码,就会发现小木板能移动,但是小球碰到小木板的时 候并不会反弹回来。先不要着急,下一节就会修正这个问题。 也许您对这类游戏使用加速度传感器并不会感到太兴奋。这个游戏本身并不精确,设 备越倾斜小木板移动得越快,因此对小球的关注变得很困难。不过这个示例的目的在于展 示如何设置加速度传感器。这个游戏实际上会使用触摸屏作为输入。因此,请删除刚才在 项目中添加的有关加速度传感器的代码,下面学习如何添加触摸支持。 5. 处理触摸屏输入 在创建项目的时候,通过 Xcode 模板创建的视图已经启用触摸支持了。我们需要做的 就是在视图代码中添加处理触摸事件的方法。这些方法为:“touchesBegan:withEvent:”、 “touchesMoved:withEvent:”和“touchesEnded:withEvent:”。用户触摸到允许触摸的组件(例 iPhone 游戏开发入门经典—— 也适用于 iPad 42 如,本示例中的视图)的时候,Cocoa Touch 会调用这些方法。 添加这些方法之前,首先需要在视图控制器中添加一个实例变量,通过这个实例变量 跟踪用户在屏幕上拖动手指的方式。程序清单 3-14 展示了需要在头文件中添加的代码。将 程序清单中加粗的代码添加到 IVBrickerViewController.h 文件中。 程序清单 3-14 在视图控制器的头文件中添加 touchOffset 实例变量 #import @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; UIImageView *ball; CGPoint ballMovement; UIImageView *paddle; float touchOffset; } 马上就会解释 touchOffset 实例变量的作用。 既然已经修改了头文件,下面就在视图控制器的实现中添加触摸事件处理代码,如程 序清单 3-15 所示。 程序清单 3-15 在视图控制器中实现触摸处理 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [[event allTouches] anyObject]; touchOffset = paddle.center.x - [touch locationInView:touch.view].x; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [[event allTouches] anyObject]; float distanceMoved = ([touch locationInView:touch.view].x + touchOffset) - paddle.center.x; float newX = paddle.center.x + distanceMoved; if (newX > 30 && newX < 290) paddle.center = CGPointMake( newX, paddle.center.y ); if (newX > 290) paddle.center = CGPointMake( 290, paddle.center.y ); if (newX < 30) paddle.center = CGPointMake( 30, paddle.center.y ); } 程序清单 3-15 中所示的第一个方法为“touchesBegan:withEvent:”,这个方法在用户触 摸到屏幕的时候被调用。这个方法将小木板的中心位置和用户在屏幕上触摸的位置之间的 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 43 水平距离存储在 touchOffset 实例变量中。从中可以得到用户第一次触摸屏幕从而开始移动 小木板的位置。 第二个方法为“touchesMoved:withEvent:”,这个方法在“touchesBegan:withEvent:”之 后当用户拖动手指的时候调用。这段代码首先读取第一个方法存储的 touchOffset 值,将其 加到当前 touchesMoved 事件的水平位置上,然后再减去小木板的中心位置。通过这种方法 可以有效地跟踪手指相对初始触摸事件的左右位移。然后,将这个水平距离加到小木板的 当前位置上。这段代码可以非常流畅地移动小木板。 运行修改后的最新代码。可以发现,可以快速地将小木板从屏幕的一边移到另一边, 而不用将视线离开小球。相对于前面的加速度传感器版本,这个版本是一个巨大的改进。 6. 对象何时碰撞 到目前为止在游戏中,小球碰到小木板之后直接穿越过去,好像小木板不存在一样。 原因很简单:小球并不知道小木板的存在。小木板的动画代码只检查是否碰到了视图的边 缘。下面添加一些代码,使得“animateBall:”方法能够检测小木板的位置,并且在小球碰 到小木板的时候做出正确的反应。向 IVBrickerViewController.m 文件添加程序清单 3-16 中 所示的粗体代码。 程序清单 3-16 在“animateBall:”方法中添加小木板和小球的碰撞检测代码 - (void)animateBall:(NSTimer *)theTimer { ball.center = CGPointMake(ball.center.x+ballMovement.x, ball.center.y+ballMovement.y); BOOL paddleCollision = ball.center.y >= paddle.center.y - 16 && ball.center.y <= paddle.center.y + 16 && ball.center.x > paddle.center.x - 32 && ball.center.x < paddle.center.x + 32; if(paddleCollision) { ballMovement.y = -ballMovement.y; if(ball.center.y >= paddle.center.y - 16 && ballMovement.y < 0) { ball.center = CGPointMake(ball.center.x, paddle.center.y - 16); } else if (ball.center.y <= paddle.center.y + 16 && ballMovement.y > 0) { ball.center = CGPointMake(ball.center.x, paddle.center.y + 16); } else if (ball.center.x >= paddle.center.x - 32 && ballMovement.x < 0) { ball.center = CGPointMake(paddle.center.x - 32, ball.center.y); } else if (ball.center.x <= paddle.center.x + 32 && ballMovement.x > 0) { ball.center = CGPointMake(paddle.center.x + 32, ball.center.y); } } if(ball.center.x > 310 || ball.center.x < 16) ballMovement.x = -ballMovement.x; if(ball.center.y > 444 || ball.center.y < 32) ballMovement.y = -ballMovement.y; } iPhone 游戏开发入门经典—— 也适用于 iPad 44 程序清单 3-16 中的代码会检测小球是否撞到了小木板,然后使小球向相反的方向弹 去。由于小球每次在任何方向上的移动距离为 4 像素,因此程序清单中的第 13~21 行代码 负责检测小球是否卡在小木板中。 到目前为止,虽然这个程序已经具有完整的交互功能了,但是它还差一些东西。现在 这个程序只是一个图形演示,而不是一个游戏。还需要添加更多的代码使得这个示例成为 一个游戏。 除了动画和交互之外,游戏还需要包含玩家必须达到的一个或多个目标。因此需要设 置一组失败条件,使得游戏能够结束。还需要设置一些获胜条件,最终实现游戏的目标。 7. 失败条件 典型的击砖块游戏唯一的失败条件是没有接住小球,并导致小球碰到屏幕底边缘。当 小球碰到屏幕底边缘的时候,该游戏逻辑停止小球的运动,并通知玩家他输了。然后将小 球重置到屏幕中心并重新开始移动小球。 通常,在一个游戏会话开始的时候,游戏会设置一定数量的生命。在击砖块的游戏中, 小球每触碰一次屏幕底边缘,游戏代码就将生命值减一。一旦玩家用完了所有的生命,游 戏就结束。 要实现统计和显示生命数的逻辑,首先需要添加一个实例变量来存储当前的生命数, 可以使用一个整型变量,名为 lives,再添加一个 UILabel 类型的属性以显示这个值。还需 要另外一个 UILabel 属性来向用户显“Game Over”消息和任何其他消息。我们首先编写这 一部分的代码,然后在后面的小节再处理得分和获胜的条件。程序清单 3-17 展示了开始实 现这些游戏改进需要对类接口所做的更改。将加粗的代码添加至 IVBrickerViewController.h 文件中。 程序清单 3-17 更改视图控制器头文件,以添加对示例程序中游戏生命 和用户消息的支持 UIImageView *paddle; float touchOffset; int lives; UILabel *livesLabel; UILabel *messageLabel; BOOL isPlaying; NSTimer *theTimer; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @property (nonatomic, retain) IBOutlet UIImageView *ball; @property (nonatomic, retain) IBOutlet UIImageView *paddle; @property (nonatomic, retain) IBOutlet UILabel *livesLabel; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 45 @property (nonatomic, retain) IBOutlet UILabel *messageLabel; - (void)startPlaying; - (void)pauseGame; 程序清单 3-17 向视图控制器添加了 4 个实例变量。生命和两个标签的含义之前已经讨 论过了。使用 isPlaying 在触摸事件处理过程中判断游戏是否已经暂停。添加另一个实例变 量用作定时器实例。 程序清单的最后是标签使用的两个新属性。然后还添加了两个方法的声明:startPlaying 和 pauseGame。这两个方法用于清理和整理一些代码,避免 viewDidLoad 方法被这些代码 弄乱。 既然更改完视图控制器接口之后,就下面更改视图控制器的实现。打开 IVBrickerView- Controller.m 文件,然后按照程序清单 3-18 中加粗的代码进行修改。 程序清单 3-18 修改 IVBrickerViewController.m 文件以匹配代码中的这些更改 @synthesize paddle; @synthesize livesLabel; @synthesize messageLabel; - (void)dealloc { [scoreLabel release]; [ball release]; [paddle release]; [livesLabel release]; [messageLabel release]; [super dealloc]; } 和前面对代码的更改一样,首先合成头文件中的属性,然后在 dealloc 方法中释放这些 属性。这些合成的属性可以用在 Interface Builder 中将 UI 组件连接到代码上。 在 Interface Builder 中打开 IVBrickerViewController.xib 文件,将这些新标签添加到 UI 中。从 Library 窗口中将这些标签拖动到视图中,然后按照下面的步骤对其进行配置: ? 将一个标签放置在左上方边缘。在 Inspector 的 Attributes 选项卡中,将 Text 属性 设置为“Lives:”。取消选择 Font Size 属性的 Adjust to Fit 选项。在 Size 选项卡中, 分别将 X 和 Y 属性更改为 45 和 10。然后将宽度改为 44。 ? 然后将另外一个标签放在“Lives:”标签的右侧。在 Attributes 选项卡中,将 Text 属性设置为 3。取消选择 Font Size 属性的 Adjust to Fit 选项。在 Size 选项卡中, 分别将 X 和 Y 属性更改为 83 和 10。然后将宽度改为 20。 ? 然后将第 3 个标签放在小木板的正上方。在 Attributes 选项卡中,将 Text 属性设置 为 Game Over。将 Alignment 属性设置为 Centered(Layout 右侧中间那个按钮,在 iPhone 游戏开发入门经典—— 也适用于 iPad 46 Line Breaks 选择器正下方)。取消选择 Font Size 属性的 Adjust to Fit 选项。单击 Font 属性(当前设置为“Helvetica, 17.0”),打开一个字体选择器窗口。选择 Collections 中的 English,Family 中的 Helvetica,Typeface 中的 Oblique,然后将 Size 设置为 36。关闭字体选择器。在 Size 选项卡中,分别将 X 和 Y 属性设置为 159 和 353。 然后将宽度设置为 310,高度设置为 43。 一旦设置好这 3 个标签的位置和大小之后,就将它们连接至视图控制器中的属性上。 右击 File's Owner,打开 Outlets and Referencing Outlets 窗口。将 livesLabel 属性连接至名 为 Label (3)的组件上,将 messageLabel 属性连接至名为 Label (Game Over)的组件。图 3-10 展示了这 3 个标签以及它们的相对位置。 图 3-10 视图中的 3 个新标签:一个是文本“Lives:”的占位符,一个用于显示 玩家还剩下的生命数,另一个用于显示用户消息,例如“Game Over” 现在,对视图控制器类和 UI 的更改已经完成,下面开始更改实现,使游戏栩栩如生。 这个改动比较大,因此作者 PJ Cabrera 将改动分割在两个程序清单中。程序清单 3-19 中的 粗体代码展示了 IVBrickerViewController.m 文件的前一半更改。 程序清单 3-19 对 IVBrickerViewController.m 文件中 viewDidLoad 方法的更改 - (void)viewDidLoad { [super viewDidLoad]; [self startPlaying]; } - (void)startPlaying { if (!lives) { lives = 3; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 47 score = 0; } scoreLabel.text = [NSString stringWithFormat:@"%05d", score]; livesLabel.text = [NSString stringWithFormat:@"%d", lives]; ball.center = CGPointMake(159, 239); ballMovement = CGPointMake(4,4); // 选择小球是从左向右运动还是从右向左运动 if (arc4random() % 100 < 50) ballMovement.x = -ballMovement.x; messageLabel.hidden = YES; isPlaying = YES; [self initializeTimer]; } - (void)pauseGame { [theTimer invalidate]; theTimer = nil; } - (void)initializeTimer { if (theTimer == nil) { float theInterval = 1.0f/30.0f; // 已将"animateBall:"重命名为 gameLogic theTimer = [NSTimer scheduledTimerWithTimeInterval:theInterval target:self selector:@selector(gameLogic) userInfo:nil repeats:YES]; } } 相比之前的版本,第一个重大更改是 viewDidLoad 方法。在之前的版本中,大部分初始 化代码都放在这个方法中。现在将这些代码重构到一个名为 startPlaying 的新方法中了。这么 做的理由是:现在初始化代码需要在多处被调用。在下列情形下游戏初始化代码会被调用: ? 玩家第一次启动应用程序的时候。 ? 玩家丢失一条生命重新开始游戏的时候。 ? 玩家丢失 3 条生命开始新游戏的时候。 将初始化代码放到一个方法内以便从多处调用的做法比将这些代码复制和粘贴到多处的 做法要好。复制和粘贴代码的时候,增加了每次需要更改已复制的代码时出现 bug 的几率。 startPlaying 方法首先将玩家的生命数重置为 3,然后它将分数设置为 0(如果生命数到 达 0)。之后更新屏幕上的分数和生命数标签。和之前版本的初始化代码一样,它将 ballMovement 实例变量设置为垂直方向和水平方向均为 4 像素,并且将小球放在屏幕的中 心。另外,这段代码还通过一个随机数来决定动画开始的时候小球向左还是向右移动。 初始化代码然后隐藏消息标签,并将 isPlaying 标记设置为 YES。最后, 调用 initializeTimer 初始化 NSTimer 实例。注意,“animateBall:”方法已经被重命名为 gameLogic, 详见下面的程序清单。 程序清单 3-20 列出了视图控制器实现代码中的其他更改。 iPhone 游戏开发入门经典—— 也适用于 iPad 48 程序清单 3-20 IVBrickerViewController.m 的其他更改 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (isPlaying) { UITouch *touch = [[event allTouches] anyObject]; touchOffset = paddle.center.x - [touch locationInView:touch.view].x; } else { [self startPlaying]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (isPlaying) { UITouch *touch = [[event allTouches] anyObject]; float distanceMoved = ([touch locationInView:touch.view].x + touchOffset) - paddle.center.x; float newX = paddle.center.x + distanceMoved; if (newX > 30 && newX < 290) paddle.center = CGPointMake( newX, paddle.center.y ); } } // 已将"animateBall:"重命名为 gameLogic - (void)gameLogic { ball.center = CGPointMake(ball.center.x+ballMovement.x, ball.center.y+ballMovement.y); BOOL paddleCollision = ball.center.y >= paddle.center.y - 16 && ball.center.y <= paddle.center.y + 16 && ball.center.x > paddle.center.x - 32 && ball.center.x < paddle.center.x + 32; if(paddleCollision) ballMovement.y = -ballMovement.y; if (ball.center.x > 310 || ball.center.x < 16) ballMovement.x = -ballMovement.x; if (ball.center.y < 32) ballMovement.y = -ballMovement.y; if (ball.center.y > 444) { [self pauseGame]; isPlaying = NO; lives--; livesLabel.text = [NSString stringWithFormat:@"%d", lives]; if (!lives) { messageLabel.text = @"Game Over"; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 49 } else { messageLabel.text = @"Ball Out of Bounds"; } messageLabel.hidden = NO; } } 程序清单 3-20 中的触摸事件处理代码检查 isPlaying 标记,以判断小球是否正在运动。 当把 isPlaying 标记设置为 YES 的时候,触摸事件处理代码和之前执行的步骤一致。当把 isPlaying 标记设置为 NO,touchesBegan 事件处理代码重启游戏。另外当把 isPlaying 标记 设置为 NO 的时候,touchesMoved 事件处理代码忽略触摸事件。 如前所述,代码中的另外一个更改是将“animateBall:”方法重命名为 gameLogic。这 个方法除了制作小球的动画效果之外还完成一些其他的任务。现在当小球碰到小木板,或 碰到屏幕的上、左和右边缘的时候它会弹回。当小球碰到屏幕底部边缘的时候,这段代码 会停止游戏,具体方法是:在 pauseGame 方法中使游戏逻辑定时器失效、将 isPlaying 标志 设置为 NO、递减 lives 计数器、并显示剩下的生命数。同时,检测玩家是否还剩下生命, 如果不剩下,则显示“Game Over”消息。否则,提示玩家小球出界。一旦用户触碰屏幕 游戏就继续。 现在我们已经添加完了处理失败条件的代码,下面讨论如何实现获胜条件。 8. 获胜条件 在击砖块的游戏中,小球击中彩色砖块的时候得分。通常,击中不同颜色的砖块可以 得到不同的分值。如果在一行中击中多个砖块,还能得到奖励分——砖块越多,奖励越多。 在消耗完所有生命之前击中所有的彩色砖块则获胜。 目前,我们的游戏还不具备任何获胜条件。首先,需要在屏幕上添加一个砖块网格, 为每一个砖块使用一个不同的图像视图对象。然后,需要在游戏逻辑中添加代码,检测小 球是否弹到其中一个砖块,并且增加适当的分数。 首先看一下如何高效地显示一个图像视图网格。 1) 通过编程方式加载和显示图像 到目前为止,我们向示例程序中添加图像视图的方式都是直接在 Interface Builder 中拖 动组件。这种方法非常方便,省去了许多样板代码。但是如果游戏需要许多不同的图像, 在 Interface Builder 中把它们全部添加到视图就非常不实用:需要仔细地放置每一个图像视 图,然后和代码中的属性建立连接,这样才能根据需要在游戏中操作这些图像视图。 通过编写代码的方式创建和操作 UIImage和 UIImageView是 iPhone应用程序加载和显 示图像的另一种方式。虽然本章使用了很多 UIImage 和 UIImageView,但是并没有通过代 码直接实用,而是借助 Interface Builder 的帮助进行操作的。 UIImage 实例是从不同图像格式文件加载的图像数据的表示形式。创建 UIImage 实例 的方式通常为从 iPhone 的闪存中加载文件或者从 URL 下载图像。程序清单 3-21 展示了如 何从应用程序包中加载图像。 iPhone 游戏开发入门经典—— 也适用于 iPad 50 程序清单 3-21 从应用程序包中加载图像 UIImage* myImageObj; NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"myImage" ofType:@"png"]; // 以这种方式加载 myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath]; // 或以这种方式加载 myImageObj = [UIImage imageNamed:imagePath]; 注意: 可以自由选择[[UIImage alloc]initWithContentsOfFile:]或[UIImage imageNamed:]来加 载图像,但是要注意的是,有时候[UIImage imageNamed:]倾向于保留图像的时间比需要的 时间要长。3.5 节将展示如何通过“loadImage:”方法对 UIImage 缓存进行编码和归档。这 个缓存可以在内存紧张的时候释放图像。 UIImage 实例是不可变的,也就是说,这个对象没有提供更改图像属性的方法,例如高 度、宽度和透明度等;只能获取图像的属性。通过这个对象也不能直接访问图像数据。也就是 说,不能在 UIImage 上绘画,也不能更改其任何像素(iPhone 支持在视图的图形上下文中绘画, 并且支持将该绘画转换为一个 UIImage 对象。对于绘画需要使用其他 API,详情请见第 4 章)。 图像对象本身是不可以显示的。要显示一幅图像,需要创建 UIImageView 类的一个实例, 并且通过 UIImage 类的一个实例来对其初始化。一旦创建 UIImageView 对象之后,就可以把这 个对象添加到一个视图中以显示它。程序清单 3-22 展示了如何通过 UIImage 类的一个实例来初 始化一个 UIImageView 实例,并将其添加到一个视图中(在本例中视图的名称为 mainView)。 程序清单 3-22 通过之前加载的图像对象来初始化一个图像视图, 然后将图像视图添加到一个视图中 UIImageView* myImgView = [[UIImageView alloc] initWithImage:myImageObj]; [mainView addSubview:myImgView]; 尽管这个示例将图像视图实例存储在一个变量中,但是可以将这个对象存储在任何位 置,例如数组、集合或 Cocoa 中可用的其他任何集合对象中。 通过编程方式创建图像视图的缺点在于:只能通过代码来操作这些图像视图,失去了 通过 Interface Builder 可视化设计的便捷性。但是失去的便捷性带来的是灵活性。不仅可以 创建足够多的图像视图(只要内存允许),还能随意修改图像视图的属性,例如大小、位置、 透明度和可见性(隐藏或显示)。这些属性和 Interface Builder 中看到的那些属性是一致的。 既然我们已经学习了如何通过代码的方式创建图像视图,下面就可以开始利用这些技 术来完成我们的示例游戏了。 2) 创建砖块网格 首先,需要存储这些行砖块。为此,应该知道需要分配多少砖块。在这个示例中,砖 块的宽度和小木板的宽度一样,都为 64 像素。iPhone 的屏幕宽度为 320 像素,因此一行 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 51 可以并列放置 5 个砖块。砖块的高度大约为小木板高度的 3 倍。如果将砖块放在显示分数 和生命计数的位置下方,并且在游戏开始的时候屏幕中心刚好在显示小球的位置的正上方, 那么可以在屏幕上放置 4 行砖块。 为了存储砖块网格,使用一个 C 数组来保存这组砖块。需要为图像视图分配一个 5×4 的指针数组。现在您应该熟悉本书的套路了:打开 IVBrickerViewController.h 文件,将程 序清单 3-23 中加粗的代码复制进去。 程序清单 3-23 在视图控制器中为图像视图对象分配一个指针数组 UILabel *messageLabel; BOOL isPlaying; NSTimer *theTimer; #define BRICKS_WIDTH 5 #define BRICKS_HEIGHT 4 UIImageView *bricks[BRICKS_WIDTH][BRICKS_HEIGHT]; NSString *brickTypes[4]; } @property (nonatomic, retain) IBOutlet UILabel *scoreLabel; @property (nonatomic, retain) IBOutlet UIImageView *ball; @property (nonatomic, retain) IBOutlet UIImageView *paddle; @property (nonatomic, retain) IBOutlet UILabel *livesLabel; @property (nonatomic, retain) IBOutlet UILabel *messageLabel; - (void)initializeTimer; - (void)pauseGame; - (void)startPlaying; - (void)initializeBricks; @end 程序清单 3-23 中的代码声明了两个常量:BRICK_WIDTH 和 BRICKS_HEIGHT,然 后声明了一个指向图像视图的指针数组,通过常量来指定数组的大小。上述代码接着声明了 一个字符串数组来保存砖块图像的文件名。最后,声明了一个名为 initializeBricks 的方法。 在这个示例中,作者 PJ Cabrera 创建了 4 种不同颜色的砖块图像。在下载的代码中,可以 在 IVBricker finished version 文件夹内找到这些文件,文件名依次为 bricktype1.png 到 bricktype4.png。 一旦声明了砖块数组,就需要向数组内填充图像视图实例。填充的方式为:遍历砖块 数组,从 brickTypes 列表中选择一个不同的图像名来实例化每一个图像视图。除了实例化 图像视图并把它赋予数组之外,还需要指定每一个图像视图在屏幕上的位置,并将其添加到 主视图中。打开 IVBrickerViewController.m 文件,将程序清单 3-24 中的粗体代码复制进去。 程序清单 3-24 当首次加载视图时填充砖块图像视图数组 - (void)viewDidLoad { [super viewDidLoad]; iPhone 游戏开发入门经典—— 也适用于 iPad 52 [self initializeBricks]; [self startPlaying]; } - (void)initializeBricks { brickTypes[0] = @"bricktype1.png"; brickTypes[1] = @"bricktype2.png"; brickTypes[2] = @"bricktype3.png"; brickTypes[3] = @"bricktype4.png"; int count = 0; for (int y = 0; y < BRICKS_HEIGHT; y++) { for (int x = 0; x < BRICKS_WIDTH; x++) { UIImage *image = [UIImage imageNamed: brickTypes[count++ % 4]]; bricks[x][y] = [[[UIImageView alloc] initWithImage:image] autorelease]; CGRect newFrame = bricks[x][y].frame; newFrame.origin = CGPointMake(x * 64, (y * 40) + 100); bricks[x][y].frame = newFrame; [self.view addSubview:bricks[x][y]]; } } } - (void)startPlaying { 在程序清单 3-24 的代码中,从 viewDidLoad 方法 中调用了一个名为 initializeBricks 的方法。然后在 initializeBricks 方法中,向 brickTypes 数组中每个元素 赋予了一个不同的文件名。 程序清单 3-24 中剩下的代码循环遍历了 5×4 的 砖块网格,然后为每一个砖块分配了一个图像视图, 并向图像视图指定了 4 种不同颜色砖块图像中的一 种。然后根据图像视图在砖块网格中的位置,它计 算屏幕上的每一个位置(单位是像素)。位置是根据砖 块图像为 64 像素×40 像素计算的。代码将 x 网格坐 标乘以 64,y 网格坐标乘以 40,得到像素的位置。 在垂直方向额外增加了 100 像素,因为砖块的第一 行应该在 Score 标签和 Lives 标签下方,而且要留一 些空间使得小球可以在标签和第一行砖块之间弹 跳。图 3-11 展示了其中显示砖块网格的游戏。 图 3-11 该游戏现在可以在屏幕上半 部分显示一个砖块网格 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 53 3) 检测图像视图的碰撞 现在,虽然这个示例游戏已经具有一个砖块网格、一个小球和一块小木板,但是在运 行的时候,还是没有办法得分和赢得游戏。还需要在“gameLogic:”方法中添加一些代码 来处理得分条件和游戏获胜的条件。这些代码需要完成以下任务: ? 检测小球何时撞到了一个砖块。 ? 小球撞击之前,使得小球以相反的方向弹回。 ? 增加分数。 ? 将砖块从网格中删除。 ? 一旦所有砖块都删除之后,停止“gameLogic:”定时器,并向玩家显示一条消息, 提示玩家她在游戏中获胜。 根据本章之前的描述,视图的 frame 属性保存了视图占有的矩形区域的坐标和大小。 Core Graphics 提供了 CGRectIntersectsRect 函数用于帮助检测是否一个视图的 frame 和另一 个视图的 frame 有重合。我们将使用这个函数来检测何时小球和一个砖块碰撞。 一旦检测到小球和一个砖块的碰撞,就需要将砖块从视图中删除。比单纯删除它的方 式视觉效果更好的方式为:将砖块在一定的时间内淡出屏幕,例如 0.5 秒的时间。实现方 式为:每次运行碰撞检测代码的时候将砖块的 alpha 属性递减 0.1。因此,在检测碰撞的时 候,需要忽略 alpha 属性不为 1.0 的砖块,因为这些砖块在之前一帧中已经被小球击中了。 发生撞击后,除了砖块应该淡出之外,小球还应该从撞击的砖块那一边弹回。例如, 如果小球以垂直向下移动并且它撞击了砖块的顶部,那么在下一帧小球应该垂直向上运动。 下面看一下碰撞检测的代码。程序清单 3-25 展示了“gameLogic:”方法中的变化和为支持 这些变化而添加的其他函数。加粗的代码表示 IVBrickerViewController.m 文件中修改的地方。 程序清单 3-25 这个程序清单展示了“gameLogic:”方法中用于检测何时击中砖块 添加的代码 - (void)gameLogic:(NSTimer *) theTimer { ball.center = CGPointMake(ball.center.x+ballMovement.x, ball.center.y+ballMovement.y); BOOL paddleCollision = ball.center.y >= paddle.center.y - 16 && ball.center.y <= paddle.center.y + 16 && ball.center.x > paddle.center.x - 32 && ball.center.x < paddle.center.x + 32; if(paddleCollision) ballMovement.y = -ballMovement.y; BOOL there_are_solid_bricks = NO; for (int y = 0; y < BRICKS_HEIGHT; y++) { for (int x = 0; x < BRICKS_WIDTH; x++) { if (1.0 == bricks[x][y].alpha) iPhone 游戏开发入门经典—— 也适用于 iPad 54 { there_are_solid_bricks = YES; if ( CGRectIntersectsRect(ball.frame, bricks[x][y].frame) ) { [self processCollision:bricks[x][y]]; } } else { if (bricks[x][y].alpha > 0) bricks[x][y].alpha -= 0.1; } } } if (!there_are_solid_bricks) { [theTimer invalidate]; isPlaying = NO; lives = 0; messageLabel.text = @"You Win!"; messageLabel.hidden = NO; } if (ball.center.x > 310 || ball.center.x < 16) ballMovement.x = -ballMovement.x; if (ball.center.y < 32) ballMovement.y = -ballMovement.y; if (ball.center.y > 444) { [theTimer invalidate]; isPlaying = NO; lives--; livesLabel.text = [NSString stringWithFormat:@"%d", lives]; if (!lives) { messageLabel.text = @"Game Over"; } else { messageLabel.text = @"Ball Out of Bounds"; } messageLabel.hidden = NO; } } - (void)processCollision:(UIImageView *)brick { score += 10; scoreLabel.text = [NSString stringWithFormat:@"%d", score]; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 55 if (ballMovement.x > 0 && brick.frame.origin.x - ball.center.x <= 4) ballMovement.x = -ballMovement.x; else if (ballMovement.x < 0 && ball.center.x - (brick.frame.origin.x + brick.frame.size.width) <= 4) ballMovement.x = -ballMovement.x; if (ballMovement.y > 0 && brick.frame.origin.y - ball.center.y <= 4) ballMovement.y = -ballMovement.y; else if (ballMovement.y < 0 && ball.center.y - (brick.frame.origin.y + brick.frame.size.height) <= 4) ballMovement.y = -ballMovement.y; brick.alpha -= 0.1; } 代码更改的部分首先声明了一个变量用于判断是否屏幕上还剩下实心砖块。把这个名 为 there_are_solid_bricks 的变量的初始值设置为 NO。代码遍历网格中每一个砖块的时候, 它会检查这个砖块是否是实心的。如果代码发现空心的砖块,则每帧递减砖块的 alpha 属性 直到它完全透明为止。 一旦找到了一个实心砖块,就设置 there_are_solid_bricks 变量为 YES。代码基本上可 以通过这个变量判断玩家是否已经在游戏中获胜。当没有砖块剩下的时候玩家获胜。 当代码找到一个实心砖块之后,它还要检查小球是否击中了一个砖块,方法为:通过 Core Graphics 的 CGRectIntersectsRect 函数判断小球的任何部分是否在砖块的区域内。如 果存在碰撞,则代码调用 processCollision 方法,作为参数将砖块对象传入。 processCollision 方法将分数递增 10 分,并显示新的分数。它还检测小球击中了砖块的 哪一面,并以正确的方向弹回小球。最后一点也很重要,这个方法递减砖块的 alpha 属性, 并将砖块标记为空心。 4) 把 NSTimer 更改为 CADisplayLink 在本章开头的时候,作者 PC Cabrera 曾经提到过 iPhone SDK 3.1 添加了 CADisplayLink 类,这个类提供了按照计划定时调用某个方法的一种新方式。虽然这个类和 NSTimer 类似, 但是运行得更精确。CADisplayLink 和 iPhone 的屏幕逻辑同步,即每秒刷新 60 次。程序清 单 3-26 和 3-27 列出了使用 CADisplayLink 需要对游戏逻辑做的修改。 程序清单 3-26 需要对 IVBrickerViewController.h 文件做的更改 #import #import @interface IVBrickerViewController : UIViewController { UILabel *scoreLabel; int score; UIImageView *ball; CGPoint ballMovement; iPhone 游戏开发入门经典—— 也适用于 iPad 56 UIImageView *paddle; float touchOffset; int lives; UILabel *livesLabel; UILabel *messageLabel; BOOL isPlaying; CADisplayLink *theTimer; #define BRICKS_WIDTH 5 #define BRICKS_HEIGHT 4 程序清单 3-26 需要对 IVBrickerViewController.m 文件做的更改 - (void)pauseGame { [theTimer invalidate]; theTimer = nil; } - (void)initializeTimer { if (theTimer == nil) { theTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(gameLogic)]; theTimer.frameInterval = 2; [theTimer addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode]; } } 程序清单 3-26 将 theTimer 实例变量的类型从 NSTimer 更改为正确的 CADisplayLink 类型。程序清单 3-27 展示了 theTimer 初始化方式的变化。NSTimer 初始化器接受调用游戏 逻辑之间的一段时间间隔作为它的其中一个参数。CADisplayLink 默认每秒运行 60 次,并 且不需要设置这个时间间隔。但是由于我们的游戏逻辑打算每秒运行 30 帧,因此将 CADisplayLink 的 frameInterval 属性设置为 2。这意味着 CADisplayLink 每隔一帧运行一次, 有效地使游戏逻辑每秒运行 30 次。 CADisplayLink 和 NSTimer 的另一个区别在于,NSTimer 接受另一个参数以设置定时 器是否重复。而把 CADisplayLink 设置为重复直到它失效。还有一个区别在于,需要将显 示链接添加到一个运行循环中,即用于处理系统事件的一个 Cocoa Touch 结构。一旦 NSTimer 初始化它就开始运行。 运行这个版本的 IVBricker 之前,首先需要将 Quartz Core 框架包含在项目。方法如下: 在 Groups and Files 面板中右击 Frameworks 组,然后从弹出菜单中选择 Add | Existing Frameworks 命令。这时可以看到一个列出了所有不同 iPhone 框架的下拉列表,如图 3-12 所 示。选择 QuartzCore. framework 并单击 Add 按钮。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 57 图 3-12 选择 QuartzCore.framework 并单击 Add 按钮 3.2.4 完成了吗 现在我们已经具有一款完整游戏的所有代码。这款游戏可以处理评分、游戏失败,以 及游戏获胜。但是有关 iPhone 编程,还有一些非常重要的话题需要讨论。本章剩下的部分 着重讨论这些和 iPhone 游戏开发有关的重要话题。 下一节讲述在玩家锁定 iPhone,或被电话和短信打断的时候应该如何处理游戏。有时 候,玩家需要暂时退出应用程序,但是之后会回到应用程序。如何处理这类事情呢? 之后,本章讲述一些涉及图像的内存管理方面的技巧和提示。 最后一点也很重要,讲述一些实现图像视图动画的不同方法。 3.3 应用程序委托事件 本章前面提到了,Cocoa Touch 应用程序是由多个对象所组成的。我们通过 Xcode 提 供的模板创建示例游戏的时候,选择的是 View-based Application 模板。这个模板带有一个 MainWindow.xib 文件,应用程序启动的时候 Cocoa Touch 会自动加载并处理这个文件。通 过加载并处理这个文件,Cocoa Touch 创建了主 UIWindow 实例、IVBrickerViewController 实例,及其带有小木板图像视图和小球图像视图的视图、UIApplication 实例,以及 IVBrickerAppDelegate 实例。 一旦创建这些实例,Cocoa Touch 就可以在应用程序委托和视图控制器中向方法发送 消息,通过这种方式应用程序的代码才能运作起来。在我们过于开始编码游戏的这个代码 模板中,应用程序通过调用应用程序委托中的“applicationDidFinishLaunching:”方法开始 iPhone 游戏开发入门经典—— 也适用于 iPad 58 其生命周期。在这个方法中,IVBrickerViewController 的视图被赋予 UIWindow 实例,从 而使窗口可见。然后 Cocoa Touch 通过 IVBrickerViewController 中的 viewDidLoad 方法发 送消息,viewDidLoad 方法的代码负责创建砖块网格、初始化分值和生命计数器并且启动 定时器为小球制作动画效果,以及处理游戏逻辑。 除了通过应用程序委托和视图控制器中的这两个方法发送消息之外,在应用程序的生 命周期内 UIKit 还会发送很多消息给应用程序委托。这些消息可以分为:低内存警告、用 户中断警告、允许从中断中恢复的消息,以及应用程序终止消息。 3.3.1 应用程序终止 UIApplicationDelegate 类定义了一个名为“applicationWillTerminate:”的方法,通常, 当用户因为按 iPhone 的 home 按钮,或因为接电话、回短信或响应来自操作系统的其他任 何类型的通知而需要退出应用的时候,这个方法会被调用。可以通过这个委托方法来保存 游戏的状态,以便用户可以在以后重新开始游戏。 在“applicationWillTerminate:”方法中,不需要考虑关闭窗口、释放视图或释放内存等操 作。在应用程序调用这个方法之后 iOS 很快会负责清理工作。当然,需要在这个方法中关闭 任何打开的文件并释放数据库句柄。如果 SQLite 数据库没有正常关闭它们就有可能会损坏。 有关保存游戏状态的更多信息,请参阅 3.6 节。要记住的重要的事情是:保存游戏的 当前状态所需的时间最多允许 2~3 秒钟。应该尽可能快尽可能快速和高效地保存游戏状态, 否则操作系统就会在保存完所有数据之前关闭应用程序,从而导致数据损坏。 3.3.2 应用程序中断 应用程序委托还定义了一个名为“applicationWillResignActive:”的方法,在用户中断 应用程序的时候调用该方法。用户接到电话或收到短信,或者设备锁定的时候应用程序会 被中断。通过“applicationWillResignActive:”消息可以在用户决定是否接听电话或回复短 信的时候暂停游戏。还应该使用这个方法来保存玩家的游戏进度。 如果用户决定不接电话或不回短信而关闭了该通知消息,Cocoa Touch 就会调用 “applicationDidBecomeActive:”方法来恢复游戏。通过这个方法来初始化在游戏中在 “applicationWillResignActive:”方法中失效的任何定时器。 程序清单 3-28 展示了 IVBricker 游戏中使用的“applicationWillResignActive:”方法和 “applicationDidBecomeActive:”方法的示例实现。这些方法放在 IVBrickerAppDelegate.m 文件中。 程序清单 3-28 “applicationWillResignActive:”方法和“applicationDidBecomeActive:” 方法的示例实现 - (void)applicationWillResignActive:(UIApplication *)application { [viewController pauseGame]; [viewController saveGameState]; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 59 } - (void)applicationDidBecomeActive:(UIApplication *)application { [viewController loadGameState]; [viewController startPlaying]; } 当游戏因为电话而中断或当手机被锁定而中断的时候,程序清单 3-28 中的“application- WillResignActive:”方法会被调用,这段代码暂停游戏并保存游戏的状态。当中断之后应 用程序准备恢复游戏的时候,“applicationDidBecomeActive:”方法会被调用,代码重新加 载游戏状态并继续游戏。 3.3.3 低内存警告 在开发 iPhone 应用程序的时候,始终需要记住的一点就是:游戏的可用 RAM 非常有 限。通常,iOS 使用大约 40MB 的内存,因此在 iPhone 2G 或 3G 上运行游戏时,可用的 RAM 不到 24MB。在 iPod touch 型号上情况会好一些,因为 iPod touch 不需要接电话或发 短信,操作系统使用的 RAM 比在 iPhone 上要少一些。iPod touch 上的游戏大约有 32MB 的可用 RAM。 要特别注意的是,如果游戏使用太多的可用内存,操作系统会强制关闭它。如果操作 系统关闭应用程序,再考虑为什么会这样就已经来不及了。很可能是因为某处发生了内存 泄漏,您需要花费一些时间来调试该问题。 当然,Cocoa Touch 会帮助处理这个问题。应用程序委托在内存耗尽之前会收到一条 消息来提示您尽量及时地清理内存。这条消息调用一个名为“applicationDidReceiveMemory- Warning:”的方法。应该通过“applicationDidReceiveMemoryWarning:”方法来释放应用程 序在应用程序委托中缓存的任何数据。 例如,假设游戏从一个 Web 服务获得数据,就可以在第一次从 Web 服务检索数据后 就将其缓存。而随后使用这个 Web 服务数据只需要访问缓存即可,不需要每次都请求 Web 服务。应用程序委托是保存这些数据的一个理想地方,而在内存不足的时候,“application- DidReceiveMemoryWarning:”方法应该将这个缓存释放。如果应用程序再次需要这些 Web 服务数据,它就应该从 Web 服务重新加载数据并缓存它直到下次内存紧张的时候。 视图控制器也会接收到一条类似的消息,调用的方法名为 viewDidUnload。在游戏的 过程中,可能需要加载多个视图,游戏的每一屏都对应一个视图。从一个屏幕切换到另一 个屏幕的时候,很有可能其他屏幕就不需要占用 RAM 了,因为它们不可见。在从屏幕中 删除一个视图之后,Cocoa Touch 调用 viewDidUnload 方法。如果视图在内存中分配了任何 对象,那么它应该在 viewDidUnload 方法中释放它们。 3.4 保存和加载游戏状态 在程序清单 3-28 中,可以看到在 iPhone 收到中断通知或它锁定的时候用于暂停游戏 iPhone 游戏开发入门经典—— 也适用于 iPad 60 的代码,以及恢复游戏的代码。这些代码调用了视图控制器中的 saveGameState 和 loadGameState 方法。这一节讨论这两个方法,并且解释一种通过 Cocoa Touch 框架保存和 加载游戏状态的技术。 Cocoa Touch 提供了一个名为 NSUserDefaults 的类,这个类可以用于保存数据和在之 后查找数据。通过 NSUserDefaults 类的 standardUserDefaults 类方法可以获得默认数据存储 的一个实例。 然后通过该 示例和 “ setObject:forKey: ”、“ setInteger:forKey: ” 和 “setDouble:forKey:”等方法来指定希望保存需要一个键和一个值的数据。之后,可以通过 默认数据存储实例和“objectForKey:”、“stringForKey:”、“integerForKey:”或“doubleForKey:” 等方法来通过键读取数据。程序清单 3-29 展示了 saveGameState 方法和 loadGameState 方 法的实现,其中使用了 NSUserDefaults。 程序清单 3-29 通过 NSUserDefaults 实现 saveGameState 方法和 loadGameState 方法 NSString *kLivesKey = @"IVBrickerLives"; NSString *kScoreKey = @"IVBrickerScore"; - (void)saveGameState { [[NSUserDefaults standardUserDefaults] setInteger:lives forKey:kLivesKey]; [[NSUserDefaults standardUserDefaults] setInteger:score forKey:kScoreKey]; } - (void)loadGameState { lives = [[NSUserDefaults standardUserDefaults] integerForKey:kLivesKey]; livesLabel.text = [NSString stringWithFormat:@"%d", lives]; score = [[NSUserDefaults standardUserDefaults] integerForKey:kScoreKey]; scoreLabel.text = [NSString stringWithFormat:@"%d", score]; } 除了在应用程序委托应用程序终止、使消息失效和重新有效的方法中使用 saveGameState 和 loadGameState 方法之外,还应该玩家游戏失败和游戏获胜的时候保存游戏状态。只要 在 gameLogic 方法的获胜和失败代码部分中插入对 saveGameState 方法的调用即可。程序 清单 3-30 以粗体的形式展示了相应的更改。 程序清单 3-30 游戏逻辑代码的更改 if (!there_are_solid_bricks) { [self pauseGame]; isPlaying = NO; lives = 0; [self saveGameState]; messageLabel.text = @"You Win!"; messageLabel.hidden = NO; } if (ball.center.x > 310 || ball.center.x < 16) ballMovement.x = -ballMovement.x; 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 61 if (ball.center.y < 32) ballMovement.y = -ballMovement.y; if (ball.center.y > 444) { [self pauseGame]; isPlaying = NO; lives--; livesLabel.text = [NSString stringWithFormat:@"%d", lives]; if (!lives) { [self saveGameState]; messageLabel.text = @"Game Over"; } else { messageLabel.text = @"Ball Out of Bounds"; viewDidLoad 方法也应该改为在游戏启动的时候加载游戏状态。程序清单 3-31 以粗体 列出了 viewDidLoad 方法的更改。 程序清单 3-31 viewDidLoad 方法在游戏启动的时候加载游戏状态所做的更改 - (void)viewDidLoad { [super viewDidLoad]; [self loadGameState]; [self initializeBricks]; [self startPlaying]; } 通过包含程序清单 3-31 中的代码之后,在下一次运行应用程序的时候就可以加载前一 次的游戏状态。 在设计游戏的时候,应该决定那些状态在用户中断的时候是有必要保存在游戏中的。 在本章这个简单的游戏示例中,我们保存了分数和生命数,这只是一个示例。我们还能保 存小球的位置和当前砖块的 alpha 值。 作为更复杂的带有很多关卡的街机游戏还应该保存当前的游戏关卡。在实时战争策略 游戏中,在当前任务中不同的检查点可以选择保存每个游戏单元的状态。检查点的目的是 尽可能地允许玩家可以恢复游戏,而不用从头重玩整个游戏或当前关。 通过自定义图像加载器管理内存 在开发游戏的时候,很有可能会加载很多图像或其他容易导致内存问题的资源。有一些 方法可以用于设计图像加载的架构,这样就可以释放被图像占用的内存。程序清单 3-32 和 3-33 展示了一个缓存的图像加载器的简单实现,您可以在您自己的应用程序中使用这个加载器。 iPhone 游戏开发入门经典—— 也适用于 iPad 62 程序清单 3-32 自定义图像加载器的界面,名为 ImageCache.h #import @interface ImageCache : NSObject { } + (UIImage*)loadImage:(NSString*)imageName; + (void)releaseCache; @end 程序清单 3-33 自定义图像加载器的实现,名为 ImageCache.m #import "ImageCache.h" @implementation ImageCache static NSMutableDictionary *dict; + (UIImage*)loadImage:(NSString*)imageName { if (!dict) dict = [[NSMutableDictionary dictionary] retain]; UIImage* image = [dict objectForKey:imageName]; if (!image) { NSString* imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:nil]; image = [UIImage imageWithContentsOfFile:imagePath]; if (image) { [dict setObject:image forKey:imageName]; } } return image; } + (void)releaseCache { if (dict) { [dict removeAllObjects]; } } @end 要使用这个缓存的图像加载器,调用 ImageCache 类的“loadImage:”类方法,将图像 的文件名作为参数传入。要释放缓存中保存的所有图像的时候,调用“releaseCache”类方法。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 63 尽管这段简单的代码确实有用,但是这是一个孤注一掷的办法。缓存了所有的图像, 在操作系统报告内存不足的时候,从缓存中卸载所有的图像。这段代码很好修改,可以修 改为保存多个不同的缓存,而不是一个全局的缓存。这样,应用程序中每个不同的视图就 可以使用一个不同的缓存,因此只需要卸载不再在屏幕上显示的视图的图像即可。 3.5 动画图像 本章前面几节介绍了如何通过 UIView、UIImage、UIImageView 和 NSTimer 来创建一 个简单的游戏。前面几节使用的图像视图都是静态的。尽管它们会根据游戏逻辑处理的事 件改变位置和透明度,但是任何特定的 UIImageView 显示的 UIImage 实例在整个游戏中 都相同。 但是有很多种类的游戏都要求能够每秒多次显示不同的图像。例如,假设游戏中有一 个小机器人在屏幕上走动和跳跃,那么这个游戏需要为机器人走动的每一步或每一次跳跃 显示一幅不同的图像。 本节将介绍了几种通过 UIImageView 每秒多次显示一系列多个图像的技术。 3.5.1 使用 UIImageView 的动画属性 通过图像视图设计动画的第一个可用技术实际上内置在 UIImageView 类内部。图像视 图有一个成为 animationImages 的属性。这个属性是 UIImage 实例的一个 NSArray。可以创 建一个图像数组,每一幅图像表示动画中一个不同的帧,并且将这个数组赋予这个属性。 然后通过图像视图的 animationDuration 属性来设置动画持续的时间。animationDuration 属 性可以接受以小数形式表示的秒值。 如果要播放动画一次,并且在最后一帧显示完之后立即停止,可以将图像视图的 animationRepeatCount 属性设置为 1。这个属性的默认值为 0,表示动画永远不停地重复下 去。还可以调用 stopAnimating 方法随时停止动画,而不用设置 animationRepeatCount 和 animationDuration 参数。 有一点要记住的是,一旦设置了 animationImages 属性动画就不会立即开始,而必须调 用图像视图的 startAnimating方法才能显示动画。一旦调用 startAnimating方法,Cocoa Touch 就会自动以每 1/30 秒显示 animationImages 数组中一幅不同的图像,直到到达 animation- Duration 属性中设置的时间间隔或调用 stopAnimating 方法的时刻,以先发生的事件为准。 程序清单 3-34 演示了如何使用 UIImageView 动画。 程序清单 3-34 使用图像视图动画 NSMutableArray *images = [NSMutableArray alloc] initWithCapacity: 30]; // 将图像加载到数组中 for (int i = 1; i <= 30; i++) { NSString *imageName = [NSString stringWithFormat: @"animation1_f%0d.png", i ]; UIImage *image = [ImageCache loadImage: imageName ]; iPhone 游戏开发入门经典—— 也适用于 iPad 64 [images addObject: image]; } // 设置图像视图的动画属性 imageView.animationImages = images; [images release]; imageView.animationDuration = 1; imageView.animationRepeatCount = 1; [imageView startAnimating]; 程序清单 3-34 中的代码假定一个名为 imageView 的变量保存了一个 UIImageView 实 例,而且它已经设置了在屏幕上显示的位置。这段代码通过加载 30 幅不同的图像,创建一 个 UIImage实例数组,这 30 幅不同的图像命名为 animation1_f01.png到 animation1_f30.png。 然后这段代码设置图像视图的 animationImages 属性。animationImages 属性保存数组的一 个副本,意味着我们的数组不再需要了,所以立即释放它。这段代码设置了 animationDuration 和 animationRepeatCount 属性,并且调用 startAnimating 方法开始动画。 使用 UIImageView 内置的这种动画技术的一个局限性在于,Cocoa Touch 只能每 1/30 秒更改一次图像显示,而不能更改这个频率。如果需要高于每秒 30 帧或低于每秒 30 帧的 速度,就需要使用另一种技术。另一个局限性在于这种方式并不十分精确。Cocoa Touch 既不能保证动画可以立即开始,也不能保证每次图像更改以 1/30 秒的间隔准确发生。Cocoa Touch 只能尽量努力保证动画的运行,但是可能会产生一些小小的延迟。 3.5.2 通过 NSTimer 实现动画 UIKit 提供的另一个动画技术是设置一个 NSTimer 来定时在每秒内更改图像属性。这 种方法的好处在于运行更精确,而且没有将动画的间隔限制为 1/30 秒。程序清单 3-35 和 3-36 展示了一个派生自 UIImageView 的类的接口和实现,这个类重写了 startAnimation 和 stopAnimation 方法,以使用 NSTimer。 程序清单 3-35 派生自 UIImageView 的类的接口, 这个类使用 NSTimer 来提高动画的精度 #import @interface BetterAnimations : UIImageView { int _frameCounter; int _repeatCounter; NSTimeInterval _animationInterval; NSTimeInterval _timeElapsed; NSTimer *_theTimer; } @property (nonatomic, readwrite) NSTimeInterval animationInterval; @end 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 65 程序清单 3-36 派生自 UIImageView 的类的实现, 这个类使用 NSTimer 来提高动画的精度 #import "BetterAnimations.h" @implementation BetterAnimations @synthesize animationInterval = _animationInterval; - (BetterAnimations*)init { if (self = [super init]) { _animationInterval = 1.0 / 30.0; _frameCounter = 0; _repeatCounter = 0; _timeElapsed = 0; _theTimer = nil; } return self; } - (void)setAnimationInterval:(NSTimeInterval)newValue { if ( (1.0 / 15.0) < newValue) { _animationInterval = 1.0 / 15.0; } else if ( (1.0 / 60.0) > newValue) { _animationInterval = 1.0 / 60.0; } else { _animationInterval = newValue; } } - (void)stopAnimating { if (_theTimer) { [_theTimer invalidate]; _theTimer = nil; } } - (void)startAnimating { if (self.animationDuration > 0 && self.animationImages && [self.animationImages count] > 0) { _frameCounter = 0; _repeatCounter = 0; _timeElapsed = 0; _theTimer = [NSTimer timerWithTimeInterval:_animationInterval target:self selector:@selector(changeAnimationImage) userInfo:nil repeats:(self.animationRepeatCount > 0)]; } } iPhone 游戏开发入门经典—— 也适用于 iPad 66 - (void)changeAnimationImage { self.image = [self.animationImages objectAtIndex:frameCounter++]; _timeElapsed += _animationInterval; if ( (_timeElapsed >= self.animationDuration || _frameCounter >= self.animationImages.length) && (0 < self.animationRepeatCount && _repeatCounter <= self.animationRepeatCount) ) { _repeatCounter++; _frameCounter = 0; } if (_repeatCounter >= self.animationRepeatCount) { [self stopAnimating]; } } @end 程序清单 3-36 中的类的使用方式和 UIImageView完全一致,但是因为使用了 NSTimer, 所以动画的运行更加精确。这个类还添加了一个名为 animationInterval 的新属性,通过这 个属性可以指定图像更改的时间间隔。它默认为 1/30 秒,但是可以更改为以下范围内的任 意分之一秒,最短 1/60 秒,最长 1/15 秒。任何小于 1/60 秒的时间都向上舍入到 1/60 秒, 任何大于 1/15 秒的时间都向下舍入到 1/15 秒。 3.5.3 通过 CADisplayLink 实现动画 第 3 种动画技术使用 CADisplayLink。根据前面的描述,由于 CADisplayLink是由iPhone 的显示屏电路触发的,因此 CADisplayLink 可以非常精确地每秒运行 60 次。这个解决方案 也派生自 UIImageView,因此使用方式和使用 UIImageView 本身的动画功能完全一致。这 个解决方案默认运行 60 帧每秒,而不是 UIImageView 的 30 帧每秒。程序清单 3-37 和 3-38 分别展示了这个类的接口和实现。 程序清单 3-37 派生自 UIImageView 的类的接口,这个类使用 CADisplayLink 来提高动画的精度 #import #import @interface MoreBetterAnimations : UIImageView { int _frameCounter; int _repeatCounter; NSTimeInterval _timeElapsed; CADisplayLink *_displayLink; } @property (nonatomic, readwrite) NSInteger frameInterval; @end 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 67 程序清单 3-38 派生自 UIImageView 的类的实现,这个类使用 CADisplayLink 来提高动画的精度 #import "MoreBetterAnimations.h" @implementation MoreBetterAnimations @synthesize frameInterval; - (MoreBetterAnimations *)init { if (self = [super init]) { _frameCounter = 0; _repeatCounter = 0; _timeElapsed = 0; _displayLink= [CADisplayLink displayLinkWithTarget:self selector:@selector(changeAnimationImage) ]; } return self; } - (NSInteger)frameInterval { if (!_displayLink) { _displayLink= [CADisplayLink displayLinkWithTarget:self selector:@selector(changeAnimationImage) ]; } return _displayLink.frameInterval; } - (void)setFrameInterval:(NSInteger)newValue { if (!_displayLink) { _displayLink= [CADisplayLink displayLinkWithTarget:self selector:@selector(changeAnimationImage) ]; } if ( 1 > newValue) { _displayLink.frameInterval = 1; } else if ( 4 < newValue) { _displayLink.frameInterval = 4; } else { _displayLink.frameInterval= newValue; } } - (void)stopAnimating { if (_displayLink) { [_displayLink invalidate]; _displayLink= nil; } } - (void)startAnimating { iPhone 游戏开发入门经典—— 也适用于 iPad 68 if (self.animationDuration > 0 && self.animationImages && [self.animationImages count] > 0) { _frameCounter = 0; _repeatCounter = 0; _timeElapsed = 0; if (!_displayLink) { _displayLink= [CADisplayLink displayLinkWithTarget:self selector:@selector(changeAnimationImage) ]; } [_displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode]; } } - (void)changeAnimationImage { self.image = [self.animationImages objectAtIndex:frameCounter++]; _timeElapsed += _displayLink.duration; if ( (_timeElapsed >= self.animationDuration || _frameCounter >= self.animationImages.length) && (0 < self.animationRepeatCount && _repeatCounter <= self.animationRepeatCount) ) { _repeatCounter++; _frameCounter = 0; } if (_repeatCounter >= self.animationRepeatCount) { [self stopAnimating]; } } @end 这段代码的使用方式和前一节的 BetterAnimations 类的使用方式相似。但是不用设置 帧之间间隔的 animationInterval 属性,这个类使用 frameInterval 属性,这个属性接受 1~4 之间的一个整数。这个属性设置了 CADisplayLink 以每秒 60 帧的速度来显示动画。 frameInterval 属性默认为 1,该值表明运行速度为每秒 60 帧。frameInterval 的值为 2 表明 动画的显示速度为 60/2,即每秒 30 帧,依此类推。 要使用 MoreBetterAnimations 类,首先要把 Quartz Core 框架添加到项目中,即同之 前把 IVBricker 的实现从 NSTimer 切换到 CADisplayLink 所做的那样(见图 3-12)。 3.6 本章小结 本章为本书剩下部分建立了基础。本章学习了 Objective-C 编程的基础,还学习了一些 使用 Xcode 创建带有标签和其他 UI 组件的 iPhone 视图的知识。还学习了如何通过 UIKit 以及其他 Cocoa Touch 技术(如 NSTimer)来创建一个简单的游戏。 第 3 章 在小屏幕上移动图像—— 使用 UIKit 控件 69 本章还介绍了一些 Cocoa Touch 向应用程序发送的不同应用程序生命周期的通知,包 括启动、加载视图、卸载视图、终止,以及被其他设备功能导致的中断。学习了如何允许 玩家在终止游戏的时候保存游戏状态,并且恢复游戏状态。最后本章介绍了图像缓存和动 画技术。 本章的示例项目为 IVBricker,这个项目的最终版本包含本章出现的所有游戏代码,一 直到 3.5 节的代码。看一看这个项目,以了解如何整合这些技术。 通过本章的知识,您应该可以开始简单的 iPhone 游戏编程了。这些技巧有助于您理解 使用本书剩下章节的高级游戏编程技术。

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

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

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

下载文档

相关文档