本节书摘来自异步社区《ios和tvos 2d游戏开发教程》一书中的第2章,第2.4节挑战,作者 【美】raywenderlich.com教程开发组,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.4 挑战
本章有3个挑战,它们都很重要。完成这些挑战,能够让你练习使用向量,并且会引入新的数学工具,而在本书的剩下内容中,你将会用到这些工具。
同样,如果遇到困难,可以从本章的资源文件中找到解决方案,但是你最好是自己能够解决它。
挑战1:数学工具
你肯定已经注意到了,在开发这款游戏的时候,经常要进行点和向量的计算,例如,把点相加和相减,求取长度值等等。我们还需要在cgfloat和double之间做很多强制转型。
在本章中,到目前为止,我们都是以内嵌的方式自行完成这些计算的。这是做事情的一种很好的方式,但是,在实际工作中,这可能变得很繁琐而且具有重复性;还容易出错。
使用iossourceswift file模板创建一个新的文件,将其命名为myutils。然后,使用如下的代码替换myutils的内容:
在swift中,可以让+、-、*和/这样的运算符作用于任何想要的类型之上。这里,我们让它们作用于cgpoint之上。
现在,可以像下面这样来把点相加了,但是,不要在任何地方添加这些代码;这里只是给出一个示例:
让我们也覆盖cgpoints上的减法、乘法和除法运算符。在myutils.swift的末尾,添加如下的代码:
现在,可以把一个cgpoint和另一个cgpoint相减、相乘和相除了。还可以将点和标量的cgfloat值相乘和相除,如下所示。同样的,不要在任何地方添加这些代码,这里只是给出一个示例。
最后,添加扩展了cgpoint的类,它带有一些辅助方法:
当这个app在32位架构的机器上运行的时候,#if/#endif语句块为true。在这种情况下,cgfloat和float具有相同的大小,因此,这段代码编写了接受cgfloat/float值(而不是默认的double)的atan2和sqrt版本;这就允许你对cgfloat/float使用atan2和sqrt,而不会受到设备架构的限制。
接下来,这个类扩展添加了一些方便的方法来获取点的长度,返回该点的一个正规化的版本(即长度为1),并且得到该点的一个角度。
使用这些辅助函数,将会使得代码更加简洁和清晰。例如,来看看movesprite(velocity:)方法:
使用*将velocity和dt相乘,避免了强制转型,简化了第1行代码。此外,使用+=运算符将精灵的位置和移动的量相加,简化了最后一行代码。
最终的结果应该如下所示:
你的挑战是,修改剩下的zombie conga以使用新的辅助代码,并且验证游戏仍然能够像预期的那样工作。当你完成之后,应该进行如下的调用,这包括对前面已经提及的两个操作符的调用:
+=运算符:1次调用;
-运算符:1次调用;
*运算符:2次调用;
normalized:1次调用;
angle:1次调用。
你将会注意到,当完成了这些工作的时候,代码变得整洁了很多,而且更加易于理解了。在后续的几章中,你将要使用我们所编写的一个数学库,它和这里所创建的数学库非常相似。
挑战2:让僵尸停下来
在zombie conga,当你点击屏幕的时候,僵尸会朝着点击的位置移动,但是随后,它会继续移动以超过该位置。
这是我们想要在zombie conga中得到的效果,但是,在其他的游戏中,你可能想要让僵尸在点击的位置停下来。你的挑战是修改游戏以做到这一点。
如下是针对一种可能的实现的一些提示:
创建一个名为lasttouchlocation的可选的属性,并且当玩家触摸场景的时候,更新这个属性。
在update()中,检查最近一次触摸的位置和僵尸的位置之间的距离。如果这个距离小于或等于僵尸将要在当前帧中移动的距离(zombiemovepointspersec * dt),那么就把僵尸的位置设置为最近一次触摸的位置,并且将其速度设置为0。否则,正常地调用movesprite(velocity:)和rotatesprite(direction:)。应该还要调用boundscheckzombie()。
为了实现这些,要用到挑战1中的辅助代码,使用一次-运算符并且调用一次length()。
挑战3:平滑移动
目前,僵尸会立即旋转以面朝点击的位置。这可能有点突兀,如果僵尸随着时间的流逝逐渐平滑地旋转以面朝新的方向的话,看上去会好很多。
为了做到这一点,需要一个新的辅助程序。将如下代码添加到myutils.swift(to typeπ, use option-p)的末尾。
如果cgfloat大于或等于0,sign()返回1,否则的话,它返回-1。
shortestanglebetween()返回两个角之间的最短的角度。这并不是将两个角相减那么简单,理由有两个:
1.角度在超过360度(2 * m_pi)之后会“舍入”。换句话说,30度和390度表示相同的角度,如图2-24所示。
2.有时候,两个角之间旋转最短的方式是向左,而有时候又是向右。例如,如果从0度开始,想要转到270度,最短的方式是转-90度,而不是转270度,如图2-25所示。我们不想让僵尸转一大圈,虽然它是僵尸,但是它并不蠢笨。

图2-25
因此,这个程序求得两个角度之间的差,去掉任何比360度大的部分,然后确定是向右旋转还是向左旋转更快。
你的挑战是修改rotatesprite(direction:),以接受并使用一个新的参数,即僵尸每秒应该旋转的弧度数。
定义如下的常量:
let zombierotateradianspersec:cgfloat = 4.0 * π
并且将该方法的签名修改为如下所示:
这里针对这个方法的实现给出一些提示:
使用shortestanglebetween()找出当前角和目标角之间的距离,称之为shortest。
根据rotateradianspersec和dt计算出在这一帧中要旋转的量,称之为amttorotate。
如果shortest的绝对值小于amttorotate,使用shortest来替代它。
将amttorotate加到精灵的zrotation中,但是先将其与sign()相乘,以便可以朝着正确的方向旋转。
不要忘了在update()中更新对旋转精灵的调用,以便它可以使用新参数。
如果你完成了所有这3个挑战,做的真是不错!你真的已经理解了如何使用“经典的”方法随着时间来更新值,从而移动和旋转精灵。
然而,经典的方法只是为了便于理解,它总是会让步于现代的方法的。
在第3章中,我们将学习sprite kit如何通过神奇的动作,让一些常见的任务变得非常容易。