《iOS和tvOS 2D游戏开发教程》——第2章,第2.4节挑战

简介:

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

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

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

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

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

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

import Foundation
import CoreGraphics

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
}

在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 * 2
let 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)))
}
#endif

extension 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如何通过神奇的动作,让一些常见的任务变得非常容易。

相关文章
|
存储 编解码 vr&ar
苹果官宣 WWDC22 : 6 月 6 日线上见!iOS 16、watchOS 9 和 tvOS 16 要来了吗?
苹果官宣 WWDC22 : 6 月 6 日线上见!iOS 16、watchOS 9 和 tvOS 16 要来了吗?
348 0
苹果官宣 WWDC22 : 6 月 6 日线上见!iOS 16、watchOS 9 和 tvOS 16 要来了吗?
|
iOS开发
iOS开发UI篇 - Quartz 2D简单使用
iOS开发UI篇 - Quartz 2D简单使用
iOS开发UI篇 - Quartz 2D简单使用
|
API 图形学 iOS开发
IOS开发应用之Quartz 2D学习指南
引用:http://mobile.51cto.com/iphone-284414.htm IOS开发应用之Quartz 2D学习指南是本文要介绍的内容,主要是来学习Quartz 2D的使用方法和操作。
1107 0
|
iOS开发
【Xamarin.iOS】tvOS简介
最近,苹果公司宣布推出AppleTV 4K,这是2015年推出的AppleTV的改进版本,为我们带来了一个在tvOS上开发的新平台。 借助支持触摸的Siri遥控器,我们有了一种与大屏幕上的应用程序进行交互的新方式,这种方式是不同的,但并不完全陌生。
1841 0
|
iOS开发
iOS - Quartz 2D 下载进度按钮绘制
1、绘制下载进度按钮 具体实现代码见 GitHub 源码 QExtension QProgressButton.h @interface QProgressButton : UIButton /// 进度值,范围 0 ~ 1 @property (nonatomic, ...
1292 0
|
iOS开发
iOS - Quartz 2D 手势截屏绘制
1、绘制手势截屏 具体实现代码见 GitHub 源码 QExtension QTouchClipView.h @interface QTouchClipView : UIView /** * 创建手势截屏视图控件,获取截屏结果 * * @par...
1403 0
|
iOS开发
iOS - Quartz 2D 画板绘制
1、绘制画板 1.1 绘制简单画板 PaintBoardView.h @interface PaintBoardView : UIView @end PaintBoardView.
1148 0
|
iOS开发 数据格式
iOS - Quartz 2D 第三方框架 Charts 绘制图表
1、Charts 简介 使用第三方框架 Charts 绘制 iOS 图表。GitHub 源码 Charts Charts 是一款用于绘制图表的框架,可以绘制柱状图、折线图、K线图、饼状图等。Charts 只有 Swift 版本。
3036 1
|
iOS开发 图形学
iOS - Quartz 2D 贝塞尔曲线
1、贝塞尔曲线 贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。
1562 0
|
存储 图形学 数据安全/隐私保护
iOS - Quartz 2D 二维绘图
1、Quartz 2D 简介 Quartz 2D 属于 Core Graphics(所以大多数相关方法的都是以 CG 开头),是 iOS/Mac OSX 提供的在内核之上的强大的 2D 绘图引擎,并且这个绘图引擎是设备无关的。
1868 0

热门文章

最新文章