博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《iOS和tvOS 2D游戏开发教程》——第2章,第2.4节挑战
阅读量:5748 次
发布时间:2019-06-18

本文共 5570 字,大约阅读时间需要 18 分钟。

本节书摘来自异步社区《iOS和tvOS 2D游戏开发教程》一书中的第2章,第2.4节挑战,作者 【美】raywenderlich.com教程开发组,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.4 挑战

本章有3个挑战,它们都很重要。完成这些挑战,能够让你练习使用向量,并且会引入新的数学工具,而在本书的剩下内容中,你将会用到这些工具。

同样,如果遇到困难,可以从本章的资源文件中找到解决方案,但是你最好是自己能够解决它。

挑战1:数学工具

你肯定已经注意到了,在开发这款游戏的时候,经常要进行点和向量的计算,例如,把点相加和相减,求取长度值等等。我们还需要在CGFloat和Double之间做很多强制转型。

在本章中,到目前为止,我们都是以内嵌的方式自行完成这些计算的。这是做事情的一种很好的方式,但是,在实际工作中,这可能变得很繁琐而且具有重复性;还容易出错。

使用iOSSourceSwift File模板创建一个新的文件,将其命名为MyUtils。然后,使用如下的代码替换MyUtils的内容:

import Foundationimport CoreGraphicsfunc + (left: CGPoint, right: CGPoint) -> CGPoint {   return CGPoint(x: left.x + right.x, y: left.y + right.y)}func += (inout left: CGPoint, right: CGPoint) {   left = left + right}

在Swift中,可以让+、-、*和/这样的运算符作用于任何想要的类型之上。这里,我们让它们作用于CGPoint之上。

现在,可以像下面这样来把点相加了,但是,不要在任何地方添加这些代码;这里只是给出一个示例:

let testPoint1 = CGPoint(x: 100, y: 100)let testPoint2 = CGPoint(x: 50, y: 50)let testPoint3 = testPoint1 + testPoint2

让我们也覆盖CGPoints上的减法、乘法和除法运算符。在MyUtils.swift的末尾,添加如下的代码:

func - (left: CGPoint, right: CGPoint) -> CGPoint {   return CGPoint(x: left.x - right.x, y: left.y - right.y)}func -= (inout left: CGPoint, right: CGPoint) {   left = left - right}func * (left: CGPoint, right: CGPoint) -> CGPoint {   return CGPoint(x: left.x * right.x, y: left.y * right.y)}func *= (inout left: CGPoint, right: CGPoint) {   left = left * right}func * (point: CGPoint, scalar: CGFloat) -> CGPoint {   return CGPoint(x: point.x * scalar, y: point.y * scalar)}func *= (inout point: CGPoint, scalar: CGFloat) {   point = point * scalar}func / (left: CGPoint, right: CGPoint) -> CGPoint {   return CGPoint(x: left.x / right.x, y: left.y / right.y)}func /= (inout left: CGPoint, right: CGPoint) {   left = left / right}func / (point: CGPoint, scalar: CGFloat) -> CGPoint {   return CGPoint(x: point.x / scalar, y: point.y / scalar)}func /= (inout point: CGPoint, scalar: CGFloat) {   point = point / scalar}

现在,可以把一个CGPoint和另一个CGPoint相减、相乘和相除了。还可以将点和标量的CGFloat值相乘和相除,如下所示。同样的,不要在任何地方添加这些代码,这里只是给出一个示例。

let testPoint5 = testPoint1 * 2let testPoint6 = testPoint1 / 10

最后,添加扩展了CGPoint的类,它带有一些辅助方法:

#if !(arch(x86_64) || arch(arm64))func atan2(y: CGFloat, x: CGFloat) -> CGFloat {    return CGFloat(atan2f(Float(y), Float(x)))}func sqrt(a: CGFloat) -> CGFloat {   return CGFloat(sqrtf(Float(a)))}#endifextension CGPoint {   func length() -> CGFloat {      return sqrt(x*x + y*y)   }   func normalized() -> CGPoint {      return self / length()   }   var angle: CGFloat {      return atan2(y, x)   }}

当这个App在32位架构的机器上运行的时候,#if/#endif语句块为true。在这种情况下,CGFloat和Float具有相同的大小,因此,这段代码编写了接受CGFloat/Float值(而不是默认的Double)的atan2和sqrt版本;这就允许你对CGFloat/Float使用atan2和sqrt,而不会受到设备架构的限制。

接下来,这个类扩展添加了一些方便的方法来获取点的长度,返回该点的一个正规化的版本(即长度为1),并且得到该点的一个角度。

使用这些辅助函数,将会使得代码更加简洁和清晰。例如,来看看moveSprite(velocity:)方法:

func moveSprite(sprite: SKSpriteNode, velocity: CGPoint) {   let amountToMove = CGPoint(x: velocity.x * CGFloat(dt),                                    y: velocity.y * CGFloat(dt))   print("Amount to move: \(amountToMove)")   sprite.position = CGPoint(      x: sprite.position.x + amountToMove.x,      y: sprite.position.y + amountToMove.y)}

使用*将velocity和dt相乘,避免了强制转型,简化了第1行代码。此外,使用+=运算符将精灵的位置和移动的量相加,简化了最后一行代码。

最终的结果应该如下所示:

func moveSprite(sprite: SKSpriteNode, velocity: CGPoint) {   let amountToMove = velocity * CGFloat(dt)   print("Amount to move: \(amountToMove)")   sprite.position += amountToMove}

你的挑战是,修改剩下的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)的末尾。

letπ = CGFloat(M_PI)func shortestAngleBetween(angle1: CGFloat,                               angle2: CGFloat) -> CGFloat {   let twoπ = π * 2.0   var angle = (angle2 - angle1) % twoπ   if (angle >= π) {      angle = angle - twoπ   }   if (angle <= -π) {      angle = angle + twoπ   }   return angle}extension CGFloat {   func sign() -> CGFloat {      return (self >= 0.0) ? 1.0 : -1.0   }}

如果CGFloat大于或等于0,sign()返回1,否则的话,它返回-1。

shortestAngleBetween()返回两个角之间的最短的角度。这并不是将两个角相减那么简单,理由有两个:

1.角度在超过360度(2 * M_PI)之后会“舍入”。换句话说,30度和390度表示相同的角度,如图2-24所示。

2.有时候,两个角之间旋转最短的方式是向左,而有时候又是向右。例如,如果从0度开始,想要转到270度,最短的方式是转-90度,而不是转270度,如图2-25所示。我们不想让僵尸转一大圈,虽然它是僵尸,但是它并不蠢笨。

f78c7f780322f2d020d10d5b3e70039b28075972

图2-25 

因此,这个程序求得两个角度之间的差,去掉任何比360度大的部分,然后确定是向右旋转还是向左旋转更快。

你的挑战是修改rotateSprite(direction:),以接受并使用一个新的参数,即僵尸每秒应该旋转的弧度数。

定义如下的常量:

let zombieRotateRadiansPerSec:CGFloat = 4.0 * π

并且将该方法的签名修改为如下所示:

func rotateSprite(sprite: SKSpriteNode, direction: CGPoint,                      rotateRadiansPerSec: CGFloat) {   // Your code here!}

这里针对这个方法的实现给出一些提示:

使用shortestAngleBetween()找出当前角和目标角之间的距离,称之为shortest。

根据rotateRadiansPerSec和dt计算出在这一帧中要旋转的量,称之为amtToRotate。
如果shortest的绝对值小于amtToRotate,使用shortest来替代它。
将amtToRotate加到精灵的zRotation中,但是先将其与sign()相乘,以便可以朝着正确的方向旋转。
不要忘了在update()中更新对旋转精灵的调用,以便它可以使用新参数。
如果你完成了所有这3个挑战,做的真是不错!你真的已经理解了如何使用“经典的”方法随着时间来更新值,从而移动和旋转精灵。

然而,经典的方法只是为了便于理解,它总是会让步于现代的方法的。

在第3章中,我们将学习Sprite Kit如何通过神奇的动作,让一些常见的任务变得非常容易。

转载地址:http://burzx.baihongyu.com/

你可能感兴趣的文章
双边过滤器(Bilateral filter)
查看>>
Android图形显示系统——下层显示4:图层合成上(合成原理与3D合成)
查看>>
Windows 10 技术预览
查看>>
Tomcat http跳转https
查看>>
一个自动布署.net网站的bat批处理实例
查看>>
tomcat 安装
查看>>
AIX:物理卷及有关概念
查看>>
我的友情链接
查看>>
Centos6.6安装选包及基础场景说明
查看>>
《从零开始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理
查看>>
java基础面试题-1
查看>>
深克隆与序列化效率的比较
查看>>
C++入门篇01
查看>>
3.python开发语言的特点
查看>>
Nginx使用Proxy_cache实现服务器端静态文件缓存
查看>>
lamp+nginx代理+discuz+wordpress+phpmyadmin搭建一
查看>>
nagios监控使用139邮箱报警
查看>>
Windows Phone 7 中各种Task解说(启动器与选择器)
查看>>
ArcGIS网络分析之Silverlight客户端最近设施点分析(四)
查看>>
罗森伯格助力2011年中国智能建筑技术发展应用论坛哈尔滨站
查看>>