【试读】《游戏开发的数学和物理》序+前言+前3章

f8g7

贡献于2014-12-15

字数:0 关键词:

内 容 提 要 本书严格选取了游戏开发中最常用的数学和物理学知识,通过游戏开发实例,配上丰富的插图,以 从易到难的顺序进行讲解。第1章到第5章分别讲解了物体的运动、卷动、碰撞检测、光线的制作、画面 切换的细分处理。这5章将2D游戏必需的知识一网打尽,同时还严格挑选了少量3D游戏编程的基础内容 以供参考。第6章系统梳理了游戏开发的数学和物理学理论。本书适合网络和手机游戏开发者阅读。 実例で学ぶゲーム開発に使える数学•物理学入門 (Jitsurei de Manabu GameKaihatsu ni Tukaeru Suugaku/Butsurigaku Nyuumon :3086-6) Copyright © 2013 by KIYOSHI KATO.  Original Japanese edition published by SHOEISHA Co., Ltd. Simplified Chinese Character translation rights arranged with SHOEISHA Co., Ltd. through CREEK & RIVER Co., Ltd. Simplified Chinese Character translation copyright © 2015 by Posts & Telecom Press. 本书中文简体字版由SHOEISHA Co., Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不 得以任何方式复制或抄袭本书内容。版权所有,侵权必究。 ◆ 著 [日] 加藤洁 译    徐 谦 责任编辑 乐 馨 执行编辑 杜晓静 责任印制 杨林杰 ◆ 人民邮电出版社出版发行 北京市丰台区成寿寺路11 号 邮编 100164 电子邮件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn 北京        印刷 ◆ 开本:800×1000 1/16 印张:16.5 字数:325千字 2015年1月第 1 版 印数:1 - 4 000册 2015年1月北京第 1 次印刷 著作权合同登记号 图字:01-2014-4164号 定价:59.00元 读者服务热线:(010)51095186转600 印装质量热线:(010)81055316 反盗版热线:(010)81055315 广告经营许可证:京崇工商广字第0021号 图书在版编目(CIP)数据 游戏开发的数学和物理 / (日) 加藤洁著 ; 徐谦译 . -- 北京 : 人民邮电出版社, 2015.1 (图灵程序设计丛书) ISBN 978-7-115-37581-0 Ⅰ. ①游… Ⅱ. ①加… ②徐… Ⅲ. ①游戏程序-程 序设计 Ⅳ. ①TP311.5 中国版本图书馆CIP数据核字(2014)第263046号 译者序 提到物理和数学,尤其是高等数学时,想必多数人的第一反应都是“好难”吧。译者本人 也是如此,在看到如同天书一般的物理数学公式时,大都会望而却步,因为下意识里我们都会 把纯理论性的物理数学问题,归纳为“科学家”才会研究的领域,从而不自觉地选择逃避。 仔细想想,物理和数学真的很难吗?绝大多数翻开这本书的朋友,都应该有中考、高考的 经历,并且一路过关斩将应付过无数次物理和数学考试。从这个角度来看,其实我们每个人都 已经具备了不错的物理和数学天赋,“好难”只是一个借口。 那么为什么我们会这样抗拒物理和数学呢?从很多朋友的反馈来看大致可以归纳为两点: 没有用、没意思。比如大学的微积分课程,只会告诉我们如何求解积分习题,却几乎没有提到 积分在现实世界中有什么实际用途;再比如我们在学校学习正态分布,只看到教科书上画着正 态分布的图形,却不知道这样的图形能做什么。 而在本书中,一切都从实例出发,不讲任何空洞或者脱离现实的理论知识。读过本书后, 就可以知道积分不仅可以用来做题,还可以精准地模拟出物体在重力作用下的运动轨迹;而原 本枯燥的正态分布,在本书中却可以转换成火山喷发一样的酷炫效果。通过本书总计超过40 个实例的讲解,不仅能学到必要的游戏开发知识,更重要的是还能让我们重新认识物理和数学 这两门学科——物理和数学绝不只是应付考试的毫无用处的纯理论知识,而是解决很多实际问 题时必不可少的工具。 作为一本游戏开发的入门书,本书也颇具特色。目前市面上的游戏开发入门书大致可分为 两种:一种好像葵花宝典这样的速成武功,按照书中的讲解一步一步地操作,最后一般都能做 出一个完整的小游戏。快则快矣,却不求甚解,很多算法原理以及细节都被一笔带过,虽然能 出成果,却仍然知其然而不知其所以然,不容易进一步提高;另一种如同易筋经,偏理论而不 重实践。即使整本书看完,可能还是不知道从何入手才能做出一个完整的游戏,入门时间太长。 本书虽然在形式上偏向后者,但是仍然重视实战,所选择的案例如物体移动、卷动、碰撞检测、 画面切换等都是游戏开发中最基础也最重要的组成部分,每个案例又有一到两个变形或进阶实 2 ︱ 译者序 例,满足不同层次读者的需要。因此如果是以游戏开发为职业目标,想扎扎实实地打好基础的 话,本书将会是一个不错的选择。 译者的本职工作侧重于Web开发,出于对游戏开发的兴趣完成了本书的翻译,如书中存 在疏漏之处,还请读者不吝指出。 最后感谢在翻译过程中给予我支持和鼓励的妻子和猫咪,同时也感谢图灵公司各位编辑的 共同努力,才让本书得以面世。希望所有对游戏开发感兴趣的读者都能从中获益。 徐 谦 2014年10月14日 这是一本通过游戏开发实例,讲解数学与物理知识的书。数学与物理,两者都是游戏开 发中不可或缺的。然而在实际的游戏制作中,并不能像在学校的物理数学课上那样,不管 三七二十一先背下一堆可能用得着的公式或解题技巧。真正的游戏开发,总是先遇到一个需要 实现的需求,然后再在寻求实现方法的过程中,去学习必要的数学和物理知识,即“先有需求, 再掌握实现需求的工具”。对于普通人来说,想必这样的学习模式才是更加自然并有效的吧。 自古以来,数学都是伴随着一些颇具实用性的目的产生并发展起来的。例如在古代,建筑 中必须要测算距离或长度,赋税或商业活动中必须计算款项、面积、体积等,数学正是在这些 现实需求中一步步发展起来的。而物理学的初衷也是用算式这种客观的语言来描述身边发生的 现象,同样起源于非常实际的需求。因此,从某种意义上来说,“先有需求,再去掌握实现需 求的工具”这种学习模式,正是顺应整个数学和物理发展过程的最自然的方式。 时至今日,数学和物理已经经历过无数才华横溢的先人的反复锤炼,称它们为集合了人类 智慧的瑰宝也不为过,它们共同支撑起了现代的科学技术,当然也是游戏以及计算机行业的基 石。但是我们在学习这样的智慧瑰宝时,却往往不明白数学和物理究竟有什么作用,只是像背 诵咒语一样强行记下公式,甚至也有不少人对其感到厌恶。其实人的本性确实如此,假如不是 对某样事物抱有兴趣,或者迫于外部压力,是很难认真地自觉学习的。即便数学和物理是人类 智慧的瑰宝,但如果在连它们能做什么都不了解的情况下去勉强学习,那也是违背人类本性的, 学习起来自然也就倍感艰难了。 本书以编程为题材,每一章节都会先提出一个需要解决的实际问题,然后针对问题通过公 式和必要的理论知识讲解解决方法。为了让这样的学习模式更加有效,书中所提出的问题应当 是真正实用的,才容易让读者产生兴趣,因此本书所涉及的问题严格选取了实际2D游戏编程中 不可缺少的技术。而本着尽量生动有趣的原则,在编撰人员的努力下,解决问题的示例程序都 采用了与实际游戏同样的图片素材。希望广大读者朋友可以籍由本书更加自然地走进门槛不低 的数学和物理世界。 前  言 2 ︱ 前言 本书大致可以有两种阅读方法。方法一,通过解决问题的实际案例入门(第1章~第5章 ), 掌握必要的数学、物理知识。这种阅读方法适合所有想编写游戏程序的读者。针对这部分读者, 本书涵盖了2D游戏开发中所涉及的几乎所有的必要知识。而对于想要开发3D游戏的读者,也 建议不要一下跳跃到3D,先以本书所涉及的2D知识为基础开始学习会更加容易入门。另一种 阅读方法是,从本书的理论部分(第6章)开始,先了解本书所涉及的数学、物理公式在实际生 活中究竟有什么作用。推荐那些在中学、大学里不得不学习理科,却又不知道学习理科有什么 用的同学们采用这种阅读方法。我本人作为一名游戏学校的讲师,一直在帮助学生将中学、大 学里的教育成果转变为游戏公司的实际生产力,充当着桥梁的作用,因此如果本书能对中学生、 大学生的理科教育有一点帮助的话,对我来说将是意外的惊喜。 在阅读本书时请注意,为了简化理论知识以外的部分,本书中的示例代码可能会违背一些 代码编写的基本规范。特别是类似将vx这样的短变量名作为全局变量的做法,在真正编程时 千万不要去模仿。 最后,对给予我执笔机会,并对内容等方面提供巨大帮助的小川史晃编辑及翔泳社的诸位, 对邀请我写作并协助策划的Amusement Media综合学院的猪狩贤一郎先生,以及对本书文字 提出很多建议的学院剧本专业的老师们,一并表示深深的感谢。 2013年11月 加藤洁 关于本书 目标读者·必要知识 本书面向的是那些想从事游戏开发,希望学习游戏开发中必要的数学和物理学知识的朋友。因 此本书是一本从基础开始,讲解简单亲切的入门书。学习本书前,最好能掌握一些 C语言的基础知识, 如果读者不具备C语言知识,建议另外准备一些C语言(或者C++)的资料,遇到不懂的部分可以 边查资料边学习。 本书的构成 本书的1~5章会介绍游戏开发中的一些常见问题及解决方法。在介绍过程中会将2D游戏必需 的知识一网打尽,同时还严格挑选出少量3D游戏编程的基础内容以供参考。读者可以通过调试示 例程序,对照书中讲解一步步学习这些程序所用到的数学和物理学知识以及数学公式的用法。 本书的第6章则对所有示例中涉及的数学和物理学知识的基础理论进行一遍系统梳理,帮助读 者更好地理解第1~5章。 正如前言中所说,本书有两种学习方法:方法一,先阅读1~5章,如果遇到不好理解的部分, 再去第6章寻求更系统的说明。方法二,先阅读第6章对所有的基础理论有大致印象,然后在1~5章 中逐一印证这些基础理论是如何运用到实际案例中去的。读者朋友可以自行选择适合自己的阅读方式。 其他特色 阅读难度 本书每小节都有形象的难易度标识,在学习时请注意参考。 RANK/easy RANK/normal RANK/hard RANK/very hard 专家级初中级 高中级 大学级 易 难 2 ︱ 关于本书 源代码 本书刊载的源代码,大多为了便于讲解进行了精简。不过书中的代码与实际的源代码文件行数 编号是一一对应的,读者可以自行下载源代码对照学习。 下载地址: https://github.com/AlloVince/physics_mathematics_skills_for_game_development/archive/trans.zip 在线查看: https://github.com/AlloVince/physics_mathematics_skills_for_game_development 另外,本书中的示例程序,除了数学和物理学知识外还使用了DirectX,这部分内容不在本书 的范围内,请读者自行参考相关资料。 示例程序 本书每小节都会给出1~3个示例程序进行讲解,可能仅在纸上阅读代码理解起来有点困难,建 议读者实际运行示例程序,并且修改源代码中的关键部分进行调试,对理解会更有帮助。 下载 示例程序均为可执行文件形式(EXE文件),同时附带源代码及图片素材,可以从上述网址进 行下载。同时作为参考案例,源代码中还有一些额外的EXE文件,这些文件没有附带源码,请读 者自行琢磨如何实现。 运行EXE 文件需要安装DirectX 11。源代码的编译、运行需要安装DirectX 11 SDK。 DirectX 11的下载请参考下面的网址,编译、运行的说明请参考本书末尾的附录部分。 DirectX 11下载地址: http://www.microsoft.com/zh-cn/download/details.aspx?id=35 运行环境 运行示例程序前请确认已经安装有以下的运行环境。 OS: Windows 7(32/64位) / Windows 8(64位) 开发环境: Visual Studio Express 2013 for Windows Desktop Visual Studio Professional 2012 / Visual Studio 2010 Professional DirectX SDK 9.29.1962 / DirectX 最终用户运行时 9.29.1974 免责声明 本书中的示例程序及源代码,已经获得出版社及作者的确认,读者可以作为普通用途使用。但 是万一由于使用不当等产生损失,作译者、翔泳社和人民邮电出版社不承担相应责任。 关于著作权 本书的示例程序、源代码,以及图片素材的著作权,归作者及翔泳社所有,未经许可请勿将其 发布到互联网。 第1章 物体的运动 1.1 让物体沿水平方向运动 【匀速直线运动、x+=v;、 v = -v】 ……………………………………………………2 1.2 通过键盘控制物体的运动 【键盘输入、斜方向移动、勾股定理】 …………………………………………9 1.3 让物体沿任意方向运动 【三角函数、正弦、余弦、弧度】 ……………………………………………… 20 1.4 在物体运动中加入重力 【抛物运动、重力加速度、计算误差、积分】 …………………………………… 28 1.5 物体随机飞溅运动 【随机数、均匀随机数、正态分布】 …………………………………………………… 36 1.6 让物体进行圆周运动 【角速度、向心力】 ……………………………………………………………… 42 1.7 [进阶]微分方程式及其数值解法 【微分方程、数值解法、欧拉法】 …………………………………… 48 第2章 卷动 2.1 将背景从一端卷动到另一端 【镜头位置、卷动幅度、比例关系】 ……………………………………… 56 2.2 让背景卷动与角色的运动产生联动 【区域坐标、画面坐标】 ………………………………………… 64 2.3 卷动由地图块组合的地图 【地图、地图块、整数的减法、移位运算、逻辑运算】 ……………………… 71 2.4 波纹式的摇摆卷动 【波纹扭曲、正弦波、波长、振幅、周期】 …………………………………………… 79 2.5 制作有纵深感的卷动 【透视、比例计算、梯形】 ………………………………………………………… 87 2.6 [进阶]透视理论 【视景体、近似】 ………………………………………………………………………… 92 第3章 碰撞检测 3.1 长方形物体间的碰撞检测 【矩形、德摩根定律】 ……………………………………………………… 98 3.2 圆形与圆形、圆形与长方形物体间的碰撞检测 【距离、勾股定理、平方比较】 ……………… 104 3.3 细长形物体与圆形物体间的碰撞检测 【点与线段的距离、内积、微分】 ………………………… 111 3.4 扇形物体的碰撞检测 【条件划分、向量的运算、向量的内分点、圆的方程式】 ………………………… 119 目  录 2 ︱ 目录 3.5 [进阶] 3D的碰撞检测 【2D、3D、维度扩展】 ………………………………………………………… 131 第4章 光线的制作 4.1 让物体向任意方向旋转(含缩放效果) 【旋转、基向量、向量加法、向量减法】 …………………… 136 4.2 任意两点间的光线投射 【向量长度、单位向量】 ……………………………………………………… 145 4.3 光线弯曲处理 【圆形、圆周长、伪影】 ………………………………………………………………… 150 4.4 实现带追踪效果的激光 【左右判定、外积、旋转速度】 ……………………………………………… 155 4.5 [进阶]绘制大幅度弯曲的曲线时的难点 【曲率、曲线的粗细、插值曲线、反射】 ………………… 162 第5章 画面切换效果 5.1 水平扫描式画面切换 【三角多边形、纹理素材、uv坐 标】 …………………………………………… 168 5.2 斜向扫描式画面切换 【向量形式的直线、剪裁】 ……………………………………………………… 175 5.3 使用带模糊效果的分界线进行画面切换 【渐变、Alpha合成】 …………………………………… 181 5.4 使用圆形进行画面切换 【避免重复渲染、环形、a值】 ……………………………………………… 186 5.5 雨刷式画面切换 【避免条件分支】 ……………………………………………………………………… 193 5.6 [进阶]多种多样的画面切换方法 【遮罩图案、可编程着色器、高斯滤波器】 ……………………… 200 第6章 游戏开发的数学和物理学基础理论 6.1 比例、一次函数及直线方程 【比例系数、斜率、截距、参数方程】 ………………………………… 204 6.2 算式展开与因式分解 【计算优先级、分配律】 ………………………………………………………… 208 6.3 二次函数、二次方程与抛物线  圆 【完全平方、求根公式、圆锥曲线】 ………………………… 213 6.4 三角函数 【直角三角形、单位园、弧度、相位】 ………………………………………………………… 218 6.5 向量与矩阵 【长度、方向、一次变换、逆变换】 ………………………………………………………… 225 6.6 微分 【变化率、微分系数、极限、合成函数】 ……………………………………………………………… 235 6.7 级数与积分 【数列、西格玛、原函数、不定积分、积分常数】 …………………………………………… 243 附 录 示例程序的编译及运行方法 …………………………………………………………… 249 ——基于Visual Studio 2013、Visual Studio 2012、Visual Studio 2010 1.1 让物体沿水平方向运动 ︱ 1 第1章 物体的运动 1.1 让物体沿水平方向运动 1.2 通过键盘控制物体的运动 1.3 让物体沿任意方向运动 1.4 在物体运动中加入重力 1.5 物体随机飞溅运动 1.6 让物体进行圆周运动 1.7 [进阶]微分方程式及其数值解法 11 让物体沿水平方向运动 匀速直线运动、x+=v;、 v = -v 物体运动中最基本的是直线运 动。在本小节,我们就来一起学习 RPG、射击、动作、解谜等所有游 戏类型中最基本的匀速直线运动吧。 RANK/easy 1.1 让物体沿水平方向运动 ︱ 3 说到物体的基本运动,请试想一下物体以一定速度沿直线运动的情况。没错,这种物体以 固定速度行进的直线运动,称为匀速直线运动。本小节就来讲解如何让物体进行匀速直线运动。 图1-1-1 匀速直线运动的程序 ●沿水平方向运动的程序 示例程序Movement_1_1.cpp是物体单纯地沿水平方向运动的程序。虽然代码有点长,但 大部分都是为了操作DirectX以在画面上演示运动,真正决定物体运动的只有以下部分(代码 清单1-1-1)。 代码清单1-1-1 决定物体运动的处理(Movement_1_1.cpp片 段) 016 int InitCharacter( void ) // 只在程序开始时调用一次 017 { 018 x = 0; // 物体的初始位置 019 v = 3; // 物体在x方向的速度 020 021 return 0; 022 } 023 024 025 int MoveCharacter( void ) // 每帧调用一次 4 ︱ 第1章 物体的运动 026 { 027 x += v; // 实际移动物体 028 029 return 0; 030 } 下面对这部分代码详细说明一下。首先,InitCharacter函数是一个只在程序初始化时被调 用一次的函数,用于设定物体的初始位置及x方向的速度。初始位置 018 x = 0; // 物体的初始位置 为0代表物体开始位于画面最左端。x方向的速度 019 v = 3; // 物体在x方向的速度 被设定为3。 之后的MoveCharacter函数是一个在画面切换时,即每帧被调用的函数。这个函数进行 的处理为 027 x += v; // 实际移动物体 即向水平方向的位置x加入速度v。在这里,v在初始设定中为3,所以每次MoveCharacter被调 用(即 每 帧)时 x坐标都会增加3。一般来说到下一个画面切换的时间即帧速率为 60 1 秒,因此程 序中物体会以每秒180像素的速度向右侧移动(参考图1-1-2)。 ・・・ 180 (1秒) 3 33 图1-1-2 物体以每秒180像素的速度移动 ●实验程序 那么大家是不是对上述内容都理解了呢?来做个实验吧。首先让我们改变一下物体的运动 速度。比如将InitCharacter函数内的 1.1 让物体沿水平方向运动 ︱ 5 019 v = 3; // 物体在x方向的速度 更改为: 019 v = 1; // 物体在x方向的速度 这样一来物体的速度将变为原来的 3 1 ,即物体将比原来更加缓慢地移动(Movement_1_1a.cpp)。 然后让我们在不改变水平运动方式的前提下,尝试把物体动作改造得稍微复杂一点。原程序中, 即使物体到达画面边缘,也不会停止,而是会直接移出画面。让我们来将其修改为:当物体碰 到画面边缘,会沿反方向弹回。为此,我们分别对InitCharacter函数及MoveCharacter函数 做以下改动。 代码清单1-1-2 修改为物体碰到画面边缘时折回(Movement_1_1b.cpp片 段) 016 int InitCharacter( void ) // 只在程序开始时调用一次 017 { 018 x = 0; // 物体的初始位置 019 v = 10; // 物体在x方向的速度 020 021 return 0; 022 } 023 024 025 int MoveCharacter( void ) // 每帧调用一次 026 { 027 x += v; // 实际移动物体 028 029 if ( x > VIEW_WIDTH - CHAR_WIDTH ) { // 物体碰到右端 030 v = -v; // 弹回 031 x = VIEW_WIDTH - CHAR_WIDTH; // 重设坐标为画面边缘 032 } 033 if ( x < 0 ) { 034 v = -v; 035 x = 0; 036 } 037 038 return 0; 039 } MoveCharacter函数增加了一些内容,不过还是从 InitCharacter函数开始按顺序说明。首先, InitCharacter函数中x方向的速度 6 ︱ 第1章 物体的运动 019 v = 10; // 物体在x方向的速度 从之前的3增加到了10。虽然速度变快了,但是由于新程序会让物体在画面边缘弹回,因此不 必担心物体会一下子从画面中消失。然后在MoveCharacter函数中使用了2个if语句,追加了 物体碰到画面左右端时弹回的处理。下面以画面右端的处理为例进行说明。 029 if ( x > VIEW_WIDTH - CHAR_WIDTH ) { // 物体碰到右端 这一行用来判断物体是否碰到画面右端。其中VIEW_WIDTH是画面的宽度,即画面右 端的x坐标。那么有人可能会想,判断物体是否碰到画面右端,只要比较一下物体的x坐标和 VIEW_WIDTH不就好了吗?事实上并没这么简单,因为电脑在绘制2D物体时,一般会以物 体的左上角作为物体坐标的原点,如果只是使物体的x坐标不超出VIEW_WIDTH,会让物体 完全移出画面之外(参考图1-1-3左)。因此当物体碰到画面右端时,为了使程序做出“已经碰到” 的判断,应当从画面右端的x坐标VIEW_WIDTH中,减去物体本身的大小CHAR_WIDTH, 然后再与物体的x坐标比较(参考图1-1-3右)。上面的讲解可能有点复杂,希望大家能正确理解。 CHAR_WIDTH x=VIEW_WIDTH x=VIEW_WIDTH 物体碰到画面右端 画面 画面 物体的坐标 物体完全超出画面 图1-1-3 物体到达画面右端时的计算 然后,当物体碰到边缘时运行下面这行处理。 030 v = -v; // 弹回 1.1 让物体沿水平方向运动 ︱ 7 这行代码负责对物体的弹回动作进行处理。具体来说就是反转速度的符号。比如程序中速 度的初始值为10,当物体碰到画面边缘时速度将变成-10,再碰到另一边时从-10变回10,因 此物体会沿反方向运动。可能有人会有疑问,既然这个if语句中判断的对象是画面右端,我们 已经知道物体是向右运动的,同时物体在向右运动的过程中碰到边缘时的v必定为10,将v变 为-10后物体就会向左运动。那么将 030 v = -v; // 弹回 直接写成 030 v = -10; // 弹回 不是更加容易理解吗?这样的硬编码是不好的,因为如果这样写,我们下次要更改移动速度时, 就不得不先在速度的初始化函数InitCharacter中找到下述设定项并进行更改, 019 v = 10; // 物体在x方向的速度 但是只更改这里的初始设定程序仍然无法正常工作。比如,假设我们在此处将初始速度减半至5, 那么物体碰到画面右端的瞬间,其速度将突然倍增为-10并进行反向运动,这就违背了我们的 意图。如果要扩展这个程序,比如新增加速度的处理等,这样硬编码的速度会让程序完全无法 修改。因此考虑到程序的可维护性及扩展性,需要将速度书写为 030 v = -v; // 弹回 最后说明下面这行。 031 x = VIEW_WIDTH - CHAR_WIDTH; // 重设坐标为画面边缘 正如注释中所写的那样,这行是为了将物体强制移动到正好与边缘接触的位置。之所以这 样处理,是由于判断物体是否碰到边缘的if语句,是在物体实际已经碰到了边缘之后才执行的。 比如,假设物体在前一帧中距离边缘还有1像素,且在当前帧中的速度达到了10,那么当物体 实际已经超出边缘9像素时,if语句才会执行。这样显然会有问题。因此无论物体在与边缘接 触的瞬间是否会超出,我们都将物体强制设置回与边缘正好接触的位置(参考图1-1-3右 )。 注 意观察就会发现,重设物体位置的语句 031 x = VIEW_WIDTH - CHAR_WIDTH; // 重设坐标为画面边缘 与检测物体是否碰到右端的if语句 029 if ( x > VIEW_WIDTH - CHAR_WIDTH ) { // 物体碰到右端 的区别只是将语句中的大于号(>)更改为了等号(=)。换个角度思考,这里的处理就相当于 8 ︱ 第1章 物体的运动 一旦做出物体接触到边缘的判断,就立即将物体强制移动至判断开始发生的位置。 至此,我们对物体碰到画面右端时弹回的处理进行了说明。这部分处理之后,代码中还有 画面左端的弹回处理,处理内容与画面右端是一样的,此处不再赘述。 12 通过键盘控制物体的运动 键盘输入、斜方向移动、 勾股定理 物体运动中最基本的是直线运 动。在本小节,我们就来一起学习 RPG、射击、动作、解谜等所有游 戏类型中最基本的匀速直线运动吧。 RANK/normalRANK/easy 后半部分前半部分 10 ︱ 第1章 物体的运动 用户通过键盘输入控制物体的运动,是无法简单地通过直线运动实现的。本小节就来讲解 包括斜方向运动在内的可通过键盘控制的物体运动。 通过用户输入来控制物体运动是所有游戏的基础,从实用性来讲是极其重要的。 图1-2-1 通过键盘输入控制物体运动的程序 ●通过键盘输入控制物体运动的程序 示例程序Movement_2_1.cpp是一个通过键盘输入控制物体运动的简单程序。这个程序有 点类似于一个极为简陋的射击游戏,按键盘的左右方向键可以移动物体。 决定物体移动的代码如下所示(代码清单1-2-1)。 代码清单1-2-1 根据键盘输入左右移动物体的处理(Movement_2_1.cpp片 段) 018 int InitCharacter( void ) // 只在程序开始时调用一次 019 { 020 // 物体的初始位置 021 x = ( VIEW_WIDTH - CHAR_WIDTH ) / 2.0f; 022 y = ( VIEW_HEIGHT - CHAR_HEIGHT ) / 2.0f; 023 024 return 0; 025 } 026 1.2 通过键盘控制物体的运动 ︱ 11 027 028 int MoveCharacter( void ) // 每帧调用一次 029 { 030 // 左方向键被按下时向左移动 031 if ( GetAsyncKeyState( VK_LEFT ) ) { 032 x -= PLAYER_VEL; 033 if ( x < 0.0f ) { 034 x = 0.0f; 035 } 036 } 037 // 右方向键被按下时向右移动 038 if ( GetAsyncKeyState( VK_RIGHT ) ) { 039 x += PLAYER_VEL; 040 if ( x > ( float )( VIEW_WIDTH - CHAR_WIDTH ) ) { 041 x = ( float )( VIEW_WIDTH - CHAR_WIDTH ); 042 } 043 } 044 045 return 0; 046 } 在初始化函数InitCharacter中,定义了物体的初始位置,如下所示。 020 // 物体的初始位置 021 x = ( VIEW_WIDTH - CHAR_WIDTH ) / 2.0f; 022 y = ( VIEW_HEIGHT - CHAR_HEIGHT ) / 2.0f; 即同时指定物体在水平方向和垂直方向的值,让物体的初始位置位于画面的中央。 然后在每帧调用的MoveCharacter 函数中实现了一个主要功能:当一些特殊按键被 按下时,物体向特定的方向移动;而除此以外的按键被按下时,则不做任何处理。因此必 须要有一个按键检测机制来判断当前特殊按键是否被按下了。为此程序中调用了一个名为 GetAsyncKeyState的函数。这个函数属于Windows API的一部分,即使没有DirectX也可 以使用。如果想通过DirectX检测键盘输入等用户输入,需要使用DirectInput函数,而此处 要检测的输入比较简单,仅使用Windows API就足够了。通过GetAsyncKeyState函数检测 左方向键是否被按下,可以参考下面这行代码。 031 if ( GetAsyncKeyState( VK_LEFT ) ) { 同理,检测右方向键时的代码为 038 if ( GetAsyncKeyState( VK_RIGHT ) ) { 12 ︱ 第1章 物体的运动 接着看MoveCharacter的内部,当左方向键被按下时, 032 x -= PLAYER_VEL; 物体的x坐标会减去常数PLAYER_VEL。对x坐标做减法就等同于物体向画面的左方移动。 但除此之外,还会进行下面这一处理。 032 if ( x < 0.0f ) { 033 x = 0.0f; 034 } 这个处理会使物体到达画面左端时不再向左移动。 右方向键被按下(即满足条件if ( GetAsyncKeyState( VK_RIGHT ) ))时的处理为 039 x += PLAYER_VEL; 物体的x坐标将增加PLAYER_VEL,物体会向右移动。此处也存在一个特殊处理,即 040 if ( x > ( float )( VIEW_WIDTH - CHAR_WIDTH ) ) { 041 x = ( float )( VIEW_WIDTH - CHAR_WIDTH ); 042 } 这样一来,物体在到达画面右端时,也不会再向右移动。另外,此处还进行了一个(float)强 制类型转换,这是由于表示坐标的变量x使用的是float类型,而VIEW_WIDTH和CHAR_ WIDTH则被定义为了int型的常数,为了比较和赋值,需要提前统一为float型。 ●多个按键的输入 接下来让我们尝试使物体不只可以左右方向移动,还可以上下方向移动(Movement_2_1a. cpp)。为此,对MoveCharacter函数做如下变更(代码清单1-2-2)。 代码清单1-2-2 使物体可以根据按键输入上下方向运动(Movement_2_1a.cpp片 段) 027 int MoveCharacter( void ) // 每帧调用一次 028 { 029 // 左方向键被按下时向左移动 030 if ( GetAsyncKeyState( VK_LEFT ) ) { 031 x -= PLAYER_VEL; 032 if ( x < 0.0f ) { 033 x = 0.0f; 034 } 035 } 036 // 右方向键被按下时向右移动 037 if ( GetAsyncKeyState( VK_RIGHT ) ) { ① 1.2 通过键盘控制物体的运动 ︱ 13 038 x += PLAYER_VEL; 039 if ( x > ( float )( VIEW_WIDTH - CHAR_WIDTH ) ) { 040 x = ( float )( VIEW_WIDTH - CHAR_WIDTH ); 041 } 042 } 043 // 上方向键被按下时向上移动 044 if ( GetAsyncKeyState( VK_UP ) ) { 045 y -= PLAYER_VEL; 046 if ( y < 0.0f ){ 047 y = 0.0f; 048 } 049 } 050 // 下方向键被按下时向下移动 051 if ( GetAsyncKeyState( VK_DOWN ) ) { 052 y += PLAYER_VEL; 053 if ( y > ( float )( VIEW_WIDTH - CHAR_WIDTH )){ 054 y = ( float )( VIEW_HEIGHT - CHAR_HEIGHT ); 055 } 056 } 057 058 return 0; 059 } 代码行数增加了不少,不过基本上只是把修改前对x坐标的处理照搬到了y坐标上而已。 具体说来代码段③的功能是,在上方向键被按下时从y坐标减去PLAYER_VEL,并使物体在 到达画面上端后不再向上移动。④的部分同理,在下方向键被按下时向y坐标增加PLAYER_ VEL,并使物体在到达画面下端后不再向下移动。 这样一来很多人都会产生疑问,如果同时按下多个按键物体将会如何移动呢?比如物体只 在水平方向移动时,同时按下左右方向键(可以看作停止运动的操作),物体会停止移动,这也 比较符合人们一般的思维习惯。而如果物体不仅左右移动而且上下移动,那么就可以将左右按 键与上下按键进行一些组合,可能性也就更多了,比如同时按上方向键与左方向键时物体会如 何运动呢?在目前的程序里,如果同时按上方向键与左方向键,即代码中的条件①与条件③同 时满足时,物体将向画面的左上角移动(参考图1-2-2)。 想必物体的上述运动大家都能够想象得出来,但问题是物体的速度会如何变化呢?比如上 方向键与左方向键同时被按下时, 031 x -= PLAYER_VEL; ② ③ ④ 14 ︱ 第1章 物体的运动 与 045 y -= PLAYER_VEL; 的处理会同时进行,那么物体的速度显然要比单方向的PLAYER_VEL更快,因为物体既在x 方向上以PLAYER_VEL的速度运动,同时又在y方向上以PLAYER_VEL的速度运动。这 种情况下,物体沿斜方向移动的速度究竟是多少呢? 首先可以明确的是,上述情况下物体的速度肯定要比PLAYER_VEL更快。但是由于物 体不是在同一方向(如x方向)上以2倍的PLAYER_VEL速度运动,而是在x方向和y方向上 分别以 PLAYER_VEL的速度运动,所以恐怕物体的真正速度又比 2倍的 PLAYER_VEL要慢。 于是可以得到下面的不等式。 vvv2pp11 这里vp 代表PLAYER_VEL,即只按一个方向键时物体的速度。v则代表两个方向键同时 被按下时物体的速度。 但是,仅有这个不等式,还是无法知道斜方向上的速度v究竟是多少,或者说v与vp 之间 究竟有怎样的关系。这个时候,我们就需要使用数学上的勾股定理了。简单地说,勾股定理就 是能通过直角三角形的两条边的长度,计算出剩下一条边的长度的定理。而这个定理在游戏中, 以计算直角三角形的斜边的长度居多。假设直角三角形的斜边长为c,两直角边长分别为a、b, 那么公式为 图1-2-2 上方向键与左方向键同时被按下时物体向左上角移动 上 上+左 左 1.2 通过键盘控制物体的运动 ︱ 15 cab222=+ 参考图1-2-3。 图1-2-3 通过两边的长度求剩余一边的长度 勾股定理:c2=a2+b2 a bc 而物体同时在x方向和y方向上以速度vp 运动的情况下,也可以套用勾股定理。首先,让 我们只考虑一帧内物体运动的距离。此时,由于物体在一帧内在x方向与y方向上移动的速度 均为vp(PLAYER_VEL),那么以这两个移动距离各自作为三角形的一边,就可以绘制出一 个等腰三角形。而由于x方向与y方向互为直角,结果物体的最终速度v就是这个等腰直角三 角形的斜边(参考图1-2-4)。 图1-2-4 通过x方向与y方向的速度求速度v vp 等腰直角三角形 x y v vp 套用勾股定理就是 vvvpp222=+ 16 ︱ 第1章 物体的运动 等式右边相加得到 vv2 p22= 因为我们最终要得到的是v而不是v2,所以等式两边都取平方根,得到 vv2 p!= 由于我们最终要求的速度不会是负值,所以 vv2 p= 即同时按下左方向键和上方向键时,物体既不会向水平方向移动,也不会向垂直方向移动,而 是会以 2 vp 的速度向左上方向移动。 2 约等于1.41421,因此物体在斜方向的运动速度约为 平时的1.4倍。在真正的游戏中(特别是2D射击类游戏),“斜方向的移动速度为原速度的1.4倍” 这种情况一般是允许出现的(视实际情况也会有特例),大家随便想想就能列举出不少这种沿斜 方向移动时速度变快的游戏。虽然以 2 倍的速度沿斜方向移动没有什么问题,但既然做到了 这种程度,我们就来尝试一下如何让斜方向的移动速度保持不变吧。如 Movement_2_1b.cpp所示, 我们对MoveCharacter函数做了以下修改(代码清单1-2-3)。 代码清单1-2-3 使斜方向的移动速度保持不变(Movement_2_1b.cpp片 段) 027 #define ROOT2 1.41421f 028 int MoveCharacter( void ) // 每帧调用一次 029 { 030 short bLeftKey, bRightKey; 031 short bUpKey, bDownKey; 032 033 bLeftKey = GetAsyncKeyState( VK_LEFT ); 034 bRightKey = GetAsyncKeyState( VK_RIGHT ); 035 bUpKey = GetAsyncKeyState( VK_UP ); 036 bDownKey = GetAsyncKeyState( VK_DOWN ); 037 // 左方向键被按下时向左移动 038 if ( bLeftKey ) { 039 if ( bUpKey || bDownKey ) { 040 x -= PLAYER_VEL / ROOT2; 041 } 042 else { 043 x -= PLAYER_VEL; 044 } 045 if ( x < 0 ) { 046 x = 0; 047 } 048 } ① 1.2 通过键盘控制物体的运动 ︱ 17 049 // 右方向键被按下时向右移动 050 if ( bRightKey ) { 051 if ( bUpKey || bDownKey ) { 052 x += PLAYER_VEL / ROOT2; 053 } 054 else { 055 x += PLAYER_VEL; 056 } 057 if ( x >= ( float )( VIEW_WIDTH - CHAR_WIDTH ) ) { 058 x = ( float )( VIEW_WIDTH - CHAR_WIDTH ); 059 } 060 } 061 // 上方向键被按下时向上移动 062 if ( bUpKey ) { 063 if ( bLeftKey || bRightKey ) { 064 y -= PLAYER_VEL / ROOT2; 065 } 066 else { 067 y -= PLAYER_VEL; 068 } 069 if ( y < 0 ) { 070 y = 0; 071 } 072 } 073 // 下方向键被按下时向下移动 074 if ( bDownKey ) { 075 if ( bLeftKey || bRightKey ) { 076 y += PLAYER_VEL / ROOT2; 077 } 078 else { 079 y += PLAYER_VEL; 080 } 081 if ( y >= ( float )( VIEW_HEIGHT - CHAR_HEIGHT ) ) { 082 y = ( float )( VIEW_HEIGHT - CHAR_HEIGHT ); 083 } 084 } 085 086 return 0; 087 } 这里首先定义了 bLeftKey、bRightKey、bUpKey、bDownKey四个变量,分别存放左、右、 上、下方向键当前是否被按下这一信息。下面主要以左方向键被按下时向左移动的代码段①为 ② ③ ④ 18 ︱ 第1章 物体的运动 例来详细说明。在这部分中,首先检查左方向键有没有被按下。 038 if ( bLeftKey ) { 当左方向键被按下时,再接着检查上下方向键有没有被按下。 039 if ( bUpKey || bDownKey ) { 如果在左方向键被按下的同时,又有上下方向键中的一个被按下,那么程序就会认为物体 在向斜方向移动并执行以下语句。 040 x -= PLAYER_VEL / ROOT2; 如果物体在x 方向和y 方向的移动速度仍然是PLAYER_VEL,那么斜方向的运动速 度就为原来的 2 倍,因此这里通过将原速度乘以 2 1 ,斜方向的运动速度就会仍然保持 PLAYER_VEL不变(参考图1-2-5)。 而如果左方向键被按下,上下方向键都没有被按下时,物体将仍然以速度PLAYER_ VEL向水平方向运动。 043 x -= PLAYER_VEL; 通过将上述处理应用到右方向键、上方向键和下方向键,最终就可以让物体在水平、垂直、 斜方向上的移动速度都固定为PLAYER_VEL。但是上面的程序只能应对两个方向键被同时 按下的情况,而没有考虑到同时按下三个方向键的情况。比如左、上、下三个方向键同时被按 下时,上下的运动被抵消,物体不会向垂直方向运动,只会向左方向运动,但是由于上下方向 键处于被按下的状态,所以物体在左方向的运动速度会变为平时的倍 2 1 。也就是说,我们无 1 2 1 2 1 x y 图1-2-5 重置物体在水平及垂直方向的移动速度为 2 1 ,使物体在斜方向上的移动速度保持不变 1.2 通过键盘控制物体的运动 ︱ 19 意中创造了一个游戏秘技,可以通过特殊操作让物体运动得比平时更慢,即同时按“上与下” 或“左与右”这样相反的方向键。但是这仅限于可以不受限制地同时按任意个键盘的方向键或 其他按键的环境。而考虑到多数游戏使用的都是游戏手柄或游戏摇杆等,这些设备从物理上就 无法同时输入相反的方向,自然也就不会存在这一秘技,因此这里省略了三个方向键同时输入 时的程序处理。如果对这部分程序心存疑虑,那就不妨自己努力打造一个不会轻易被秘技搞坏 的游戏吧。 RANK/normal 13 让物体沿任意方向运动 三角函数、正弦、余弦、弧度 我们已经学习了让物体沿水平、 斜方向运动。这次让我们再进一步, 来学习如何让物体沿任意方向运 动吧。 1.3 让物体沿任意方向运动 ︱ 21 接下来,我们一起学习如何让物体以任意速度、沿任意方向运动。1.1节介绍了如何让物 体沿水平方向运动,1.2节的后半部分介绍了如何让物体沿45度角的斜方向运动,让我们在此 基础上进一步拓展,让物体在任意方向以任意速度运动。还没有实践过物体沿水平方向及45 度角方向运动的读者,建议先完成前面两小节的程序再开始本小节的学习。 图1-3-1 使物体沿任意方向以任意速度运动的程序 假设物体的运动速度为v,那么物体沿任意方向的运动一般都可以被分拆为在x轴、y轴两 个方向上的运动。假设分拆后物体在x方向上的速度为vx,在y方向上的速度为vy,物体运动 方向与x轴的夹角为i(参考图1-3-2)。 x vx vy i v y 图1-3-2 表示物体运动的元素 实际在画面上显示物体时必须要有x坐标和y坐标,计算坐标就需要vx 和vy,而这两者则 可以通过物体的速度v与物体运动方向与x轴的夹角i 计算得到。 22 ︱ 第1章 物体的运动 ●让物体沿30度角方向运动的程序 我们将上述计算程序化,制作一个让物体沿相对x轴30度角方向运动的示例程序,请参考 Movement_3_1.cpp(只是程序中的y坐标是基于电脑画面的,与数学中常用的坐标正好相反(向 下为+),所以请注意这里应该是向斜下方运动)。这个程序中决定物体运动的是InitCharacter 函数中的以下3行代码(PI为圆周率)。 代码清单1-3-1 沿相对于x轴30度角的斜方向运动(Movement_3_1.cpp片 段) 025 fAngle = PI / 6.0f; 026 vx = PLAYER_VEL * cosf( fAngle ); // 设置初始速度 027 vy = PLAYER_VEL * sinf( fAngle ); 程序中突然出现了正弦(sin)余 弦( cos)的三角函数,为什么会用到它们呢?我们来详细 说明一下。首先可以明确的是x、y方向上的速度vx、vy 与物体的实际速度v是成一定比例的。 也就是说,当运动方向的角度一定时,比如当v变为原来的2倍时,vx 和vy 也应当变为2倍。 把这种比例关系写成具体的算式,就可以得到 vav vbv x y $ $ = =* 这里的a、b均为常数,称为比例系数。比如系数a代表v增加1时,vx 增加a。大家可能会觉 得不好理解,这里举一个具体的例子。比如,当物体在水平方向运动时, vv v 0 x y = =* 也就是说,此时的a=1、b=0,并且物体的运动方向正好是x轴的方向,所以物体运动方向的 角度i 为0。如果物体沿斜45度方向运动的话,速度正好会变为原来的 2 倍。 vv vv 2 1 2 1 x y = =* 上式的计算过程可以参考1.2节的后半部分。即如果物体运动方向的角度i 变成45度,那就是 a 2 1= 、b 2 1= 。也就是说,当i 为0时,a=1、b=0;当i 为45度时,a 2 1= 、b 2 1= 。 那么当角度i 取任意值时,a与b的值将会如何变化呢?为了得出结论,首先可以写出常 数a与b必定满足的条件。由于vx 和vy 最终会合成速度v,因此它们也满足勾股定理(参考图 1-3-3)。 1.3 让物体沿任意方向运动 ︱ 23 vv vxy222+= y x vx vy v vv vxy222+= 图1-3-3 对速度套用勾股定理 然后将vavx $= 、vbvy $= 的关系代入,得到 av bv v av bv v 222 22 22 2 $$+= +=]]gg 等式两边同时除以v2,得到 ab 122+= 很明显可以看出,上文中物体在水平、斜45度角方向运动时的a、b,都满足上面的等式关系。 让我们再仔细看看这个等式,它与半径为1的圆(即单位圆)的公式是完全一样的。即将a 作为横轴坐标、b作为纵轴坐标所得到的点连接起来,正好可以画出一个半径为1的圆。由于 vx、vy 分别是v乘以a、b得到的,而vx、vy 所决定的物体移动方向正好与a、b所做的单位圆上 的点的方向相同,因此绘制出的角度i 将如图1-3-4所示。 i x 1 1 vx vy v y 图1-3-4 单位圆与角度i 也就是说,a与b分别代表单位圆上角度i 的点的x坐标与y坐标。根据三角函数的定义, 24 ︱ 第1章 物体的运动 a与b可以表示成如下等式。 cos sin a b i i = =) 看到这两个等式,是不是会想问这是为什么呢?其实这就是三角函数中余弦函数、正弦函 数的定义。即角度i 在单位圆上的位置的x坐标称为余弦,y坐标称为正弦,这是由数学家所 定义的。从上式可以推导出 cos sin vv vv x y $ $ i i = =* 由此最终写出了示例程序中的语句。 只是示例程序Movement_3_1.cpp中仍然存在不明之处。 025 fAngle = PI / 6.0f; 即物体运动方向的角度被置为了 6 r ,也就是本小节一开始所说的30度角方向。这是由于计算机 中的三角函数(正弦余弦等)所能接受的角度,并不是以角度制为单位的(一周有360度 )。 30度 是一周的 12 1 (= 360 30 ),那么程序中就必须以 6 r这样的形式为单位来表示 12 1 周。稍加计算就能得到, 6 r 的12倍,即2r可以表示一周。如果有人质疑是否真的是这样,可以将程序中的fAngle从 6 r 更改为2 r,即将运动的角度指定为0,这样就可以看到物体会沿水平方向运动(画面右方向)。 事实上,这种将一周表示为2r的度量单位,称为弧度制。弧度代表半径为1、圆心角为i 的圆弧,其弧长为i(参考图1-3-5)。 1 这段长度为i 1 -1 i -1 图1-3-5 弧度 1.3 让物体沿任意方向运动 ︱ 25 因此完整一周的角就可以用半径为1的圆(单位圆)的周长来表示。圆周长计算公式为2rr, 一周的周长为2r×1=2r,也就是一周的角的弧度值。 那么,为什么计算机中的正弦余弦函数不使用看上去比较亲切易懂的角度,而用了看来不 够直观的弧度呢?其实在近代数学中,三角函数中几乎都使用弧度,很少会使用角度。之所以 使用弧度而不是角度作为角的度量单位,主要是关系到微积分的一些问题。如果坚持不使用弧 度的话,三角函数中与微积分相关的一些公式几乎都无法使用,还可能会引发一些严重的问题。 特别是游戏中会有与物理相关的公式,稍复杂的情况都会出现微积分的计算,因此读者朋友们 在程序中涉及角的度量时,请务必使用弧度而不要使用角度。如果在一个程序中同时使用弧度 和角度,就会非常容易出现BUG及各种问题,因此将角的度量单位无条件统一为弧度这一原则, 应当被无条件地贯彻下去。 请将角的单位统一为弧度。 ●使用弧度的程序 既然提到了弧度的话题,就来写个小程序实践一下吧。Movement_3_1.cpp中物体每次 出现都会向同一方向运动,这里做一个小修改让物体每次出现时的运动方向都不同。为此对 MoveCharacter做如下改动,如代码清单1-3-2所 示( Movement_3_1a.cpp片 段 )。 代码清单1-3-2 使用弧度让物体的运动方向每次都不同(Movement_3_1a.cpp片 段) 033 int MoveCharacter( void ) // 每帧调用一次 034 { 035 x += vx; // 实际运动 036 y += vy; 037 038 // 运动到画面外时回到初始位置 039 if ( ( x < -CHAR_WIDTH ) || ( x > VIEW_WIDTH ) || 040 ( y < -CHAR_HEIGHT ) || ( y > VIEW_HEIGHT ) ) 041 { 042 x = ( float )( VIEW_WIDTH - CHAR_WIDTH ) / 2.0f; 043 y = ( float )( VIEW_HEIGHT - CHAR_HEIGHT ) / 2.0f; 044 fAngle += 2.0f * PI / 10.0f; // 增加角的弧度 045 if ( fAngle > ( 2.0f * PI ) ) fAngle -= 2.0f * PI; // 经过一周后弧度重置 046 vx = PLAYER_VEL * cosf( fAngle ); 047 vy = PLAYER_VEL * sinf( fAngle ); 048 } 049 050 return 0; 051 } 26 ︱ 第1章 物体的运动 这个程序的关键点在于物体移出画面外时的处理。 044 fAngle += 2.0f * PI / 10.0f; // 增加角的弧度 045 if ( fAngle > ( 2.0f * PI ) ) fAngle -= 2.0f * PI; // 经过一周后弧度重置 第一行的作用是,当物体移出画面时使运动的角增加 10 1 周。因此物体出现10次后,运动方向 正好经过了一周。 这里将每次角变化的量写为了2.0f * PI / 10.0f。数学好的人肯定会说,与其写成2.0f * PI / 10.0f,直接写成PI / 5.0f不是更简单吗?如果从普通的数学题的角度来说,这确实可以简化 为PI / 5.0f。但是这里其实是有意而为之的,因为这样的写法可以一目了然地知道每次角增加 的量是一周的几分之一(换句话说就是经过几次可以转一周)。书写为2.0f * PI / 10.0f时,不用 特别计算也能知道2r为一周,将其10等分后,10次即满一周;而如果是PI / 5.0f,就需要去 心算,一周的弧度是2r,2r除以PI / 5.0f是多少等等,一点也不直观,理解起来也容易产生 问题。程序代码不光是为了计算机,更要考虑读代码的人,因此这里特意将参数写成了2.0f * PI / 10.0f。 如果再多考虑一些,写成(2.0f * PI) / 10.0f的话是不是更友好呢?众所周知,括号是可以 改变运算顺序的,比如1.0f + 2.0f * 3.0f与(1.0f + 2.0f) * 3.0f的结果是不一样的。然而(2.0f * PI) / 10.0f与2.0f * PI / 10.0f的计算结果并没有什么不同(这里暂不考虑运算精度),因为(2.0f * PI) / 10.0f与2.0f * PI / 10.0f的运算顺序本来就是一样的,对于计算机来说,怎么书写都没 有什么影响。但是如果写成(2.0f * PI) / 10.0f,2.0f * PI就被人为地划分为了一个整体,代表 一周的弧度,对于人类来说则更加容易理解,因此虽然只是多了一对括号,但却让程序更加自 然易懂了。 编程时不仅要考虑到计算机,还要考虑到读代码的人。 让我们再回到代码。请大家看一下程序的关键部分的第2行。 045 if ( fAngle > ( 2.0f * PI ) ) fAngle -= 2.0f * PI; // 经过一周后弧度重置 这一句有什么作用呢?如注释所示,这行语句会在弧度增加到比一周大时,减去一周的弧度。 其实弧度增加或减少一周,等于旋转了一周后又回到了原位,其代表的夹角方向并没有改变, 从数学角度来说,这一行代码是没有任何意义的。事实上即使将这行删除,至少在这个程序中, 也会得到相同的运行结果。 1.3 让物体沿任意方向运动 ︱ 27 那么这行代码真的只是摆设吗?并不是这样的。这行的意义在于控制角的弧度始终在 0GiG2r的范围内,不让角i无限制地增大下去。确实从数学角度来说,角i无论多大(即弧 度增加若干周)都没有问题。比如2r、4r、6r……全部表示同样的角,2.5r、4.5r、6.5r…… 全部等同于 2 r(90度)的角。数学中一个角无论多大都不会有问题,而实际上计算机用一个极 大值计算正弦余弦时却可能出问题。 因为角的弧度值在计算机内部是以浮点数的形式表示的,即形如1.2×102 这种指数形式的 数字。浮点数只用很少的位数(=很少的内存容量),就可以表示 100 000这样的大数。与此相对, 浮点数存在计算绝对值较大的数时会丢失精度的问题。比如计算10与计算100 000,计算误差 会相差一万倍。因此计算机在计算含有小数点的数字时,会受到绝对值较大的数的影响丢失精 度。如果轻视上面的问题,程序在循环中一次次增加弧度时,随着循环次数的增多,弧度增大 到某个值之后精度就会开始丢失,最终程序运行的结果可能会与预期完全不一样。因此除非有 特殊需要,我们在编程时都要注意避免产生一个极大的弧度值。此外,计算机的三角函数在处 理一个很大的弧度值时,其运算时间也可能会加长,这里就不再具体论证了。总之我们要尽可 能地控制i的取值范围在0GiG2r(或 者 -r GiGr)之内。 计算机计算不可避免的会有误差,绝对值越大误差也越大。因此应该尽可能地 使用绝对值小的数字进行计算。

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

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

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

下载文档

相关文档