1.介绍SpriteKit是Apple公司推出的一款运行于iOS和OSX的游戏开发框架。该工具不仅提供强大的图形功能,还包括易于使用的物理引擎。最重要的是,您可以使用您熟悉的工具-Swift、Xcode和InterfaceBuilder来完成这一切!你可以用SpriteKit做很多事情;然而,了解它如何工作的最好方法是用它开发一个简单的游戏。在这个由两部分组成的教程系列中,您将学习如何使用SpriteKit开发Breakout游戏。其中,加入了完整的碰撞检测技术,利用物理效果控制小球的弹跳,通过触摸拖动挡板,游戏状态控制等。2.开始作为前期准备,建议大家下载首先是本教程对应的初始项目。该项目是使用标准Xcode游戏模板创建的。所有资源和状态类都已经导入到项目中,这可以为您节省一点时间。随着您进一步阅读,您将了解有关游戏状态的更多信息。您可能希望花一些时间来熟悉整个项目。为此,运行命令“Build”和“Run”,您将在横向模式下看到一个灰色屏幕。请参考下图。3.SpriteKitVisualEditor简介让我们从配置场景文件开始。为此,请打开GameScene.sks文件。这是一个链接到您的SpriteKit场景的可视化编辑器,您可以从游戏的GameScene.swift文件访问其中已有的每个元素。首先,您将调整场景大小,使其适合您将在本教程中介绍的目标屏幕:iPhone6屏幕。为此,您可以在位于Xcode窗口右上角的属性检查器的场景部分中执行此操作。如果看不到属性检查器,可以通过View\Utilities\ShowAttributesinspector访问它。请将场景大小设置为568×320,如下图所示。[注意]如果你的资源库中包含准备适应多种屏幕缩放比例(即1x、2x、3x等)的图片等资源,SpriteKit会自动在当前运行的设备上使用正确的资源文件。现在,让我们考虑一下游戏的背景。如下图所示,从Xcode窗口右下角的对象库面板中拖出一个ColorSprite。如果看不到对象库面板,请从菜单栏中选择View\Utilities\ShowObjectLibrary。请使用属性检查器将位置更改为284,160并将其纹理设置为bg。现在,您可以生成并运行游戏项目并享受游戏的背景显示。一旦你创建了一个带背景的风景场景,就该给它添加球了!还是在GameScene.sks文件中,将一个新的ColorSprite拖到场景中。然后,将其名称更改为ball,将其纹理设置为ball,并将其位置更改为284,220。然后,将其Z位置属性值设置为2,以确保球出现在背景上。现在,构建并运行您的项目,您将看到球已经出现在屏幕上,如图所示。然而,到目前为止,我们的游戏还不能动画,因为我们还没有添加物理。四、物理引擎在SpriteKit中,你需要在两种环境中工作:你在屏幕上看到的图形世界和物理世界,它决定了物体如何移动和交互。使用SpriteKit物理引擎时,您需要做的第一件事就是根据您的游戏需要来改造世界。世界对象是使用SpriteKit时管理所有对象和物理模拟的主要对象。它还设置了物理机制加入世界对象所需的重力属性。默认重力值为-9.81,与地球实际重力值相近。因此,无论何时向世界添加一个对象,它都会掉落。配置世界对象后,您可以向世界对象添加基于物理与其交互的事物。为此,最常见的方法是创建一个精灵(图形)并设置其物理体。物体的身体属性和世界决定了物体如何运动。Body可以是受物理力影响的动态物体(如球、星星、鸟……),也可以是不受物理力影响的静态物体(平台、墙壁……)。创建Body时,您可以设置各种属性,例如形状、密度、摩擦力等。这些属性将严重影响Body在世界中的行为方式。定义Body时,您可能会担心其大小和密度的单位。在内部,SpriteKit使用公制系统(SI单位)。但是,您通常不需要担心游戏中的实际功率和质量,只要您使用一致的值即可。一旦您添加了世界中的所有物体,SpriteKit将接管控制并运行模拟。要设置第一个物理体,您需要选择刚刚添加的球节点,并从属性检查器的PhysicsDefinition部分选择BodyType下的BoundingCircle并设置以下属性值:取消选中“AllowsRotation”将Friction设置为0setRestitutionSetlinearDamping到0到1和AngularDamping到0以向球添加物理在这里,您创建一个基于体积的圆形物理Body,其属性与球精灵大小完全相同。这个身体受到外力或冲动的影响,能够与其他物体发生碰撞。下面详细描述它的属性。AllowsRotation:指定是否允许旋转。在此示例中,您不希望球旋转。摩擦力:这个属性也很简单,在我们的例子中是为了消除所有摩擦力。恢复力:指物体的弹性。值为1意味着当球与物体碰撞时它将保持完全弹性。简而言之,这意味着:球将以与最初相同的力弹回。LinearDamping:通过降低物体的线速度来模拟流体或空气的摩擦。在此示例游戏中,球在移动时不应减速。所以,上面你需要将阻尼设置为0。AngularDamping:这与线性阻尼相同,除了角速度。当您不想让球旋转时,将此值设置为可选。[注意]通常情况下,最好让物理身体与玩家所看到的非常相似。对于小球,我们有一个完美的匹配。但是,当你需要使用更复杂的形状时,要格外小心,因为非常复杂的物体意味着高性能的系统资源消耗。从iOS8和Xcode6开始,SpriteKit已经支持alphamasksbodytypes(阿尔法遮罩体类型),它会自动使用sprite的形状作为其物理身体的形状,但还是要谨慎使用,因为它也会降低系统性能。现在,让我们再次构建并运行该项目。如果您反应足够快,您应该会看到球从场景中掉落??并消失在屏幕底部,如图所示。出现此行为有两个原因:首先,场景的默认重力模拟地球重力-沿x轴的值为0,沿y轴的值为-9.8。其次,你场景的物理世界是无界的,还不能作为笼子来围住球。现在,让我们开始吧!5.关闭小球的效果现在,打开文件GameScene.swift,在didMoveToView(_:)方法末尾添加下面一行代码,在屏幕周围创建一个不可见的屏障://1letborderBody=SKPhysicsBody(edgeLoopF??romRect:self.frame)//2borderBody.friction=0//3self.physicsBody=borderBody我们来分析一下这行代码:(1)创建一个基于边的Body。与添加到球中的基于体积的物体相比,基于边缘的物体没有质量或体积,也不受外力或动量的影响。(2)我们将摩擦力设置为0,这样小球碰撞到边界障碍物时就不会减速了。相反,您想要产生完美的效果,即球沿其撞击的障碍物以相同的角度退出。(3)可以为每个节点设置物理Body。然后,将其添加到场景中。注意:SKPhysicsBody的坐标是相对于节点位置的。再次运行您的项目,您现在应该会看到球像以前一样下落,但现在它会在碰到笼子的底部边缘时反弹回来。因为你去掉了与笼子和环境的接触(Contact)的摩擦力,并将球的变形设置为完全弹性的,球就会永远那样弹跳。为了完成球的运动,让我们移除重力并施加一个脉冲,使其永远沿着屏幕弹跳和下落并继续前进。6.永久弹性运动现在,让球滚动(实际上是弹跳)。在文件GameScene.swift中,在上面添加的行之后立即添加以下代码:physicsWorld.gravity=CGVector(dx:0.0,dy:0.0)letball=childNodeWithName(BallCategoryName)as!SKSpriteNodeball.physicsBody!.applyImpulse(CGVector(dx:2.0,dy:-2.0))这个新代码首先从场景中移除所有重力,然后从场景的子节点中检索球并应用脉冲效果。脉冲对物理体施加直接力,使其沿特定方向移动(在这种情况下,向右斜对角)。一旦球被设置为移动,它就会简单地从屏幕上弹开,因为你刚刚添加了障碍部分!现在,是时候再试一次了!当您编译并运行该项目时,您应该会看到一个小球在屏幕上弹跳-太棒了!7.添加边框你不能把它称为没有边框的方块游戏,对吧?现在,打开GameScene.sks文件以使用可视化编辑器生成桨(及其伴随的物理Body),其方式与将ColorSprite放置在场景底部中间的方式大致相同,并设置以下属性值:=paddleTexture=paddle.pngPosition=284,30ZPosition=3BodyType>Boundingrectangle取消选中DynamicFriction:0Restitution:1显然,这里的大部分选项与创建球时使用的选项类似。但是,这次您使用Boundingrectangle来形成物理Body,以便更好地匹配矩形边框。此处通过关闭动态选项,挡板设置为静态。这将确保挡板不会受到外力和冲击的影响。您很快就会明白为什么这很重要。如果您现在构建并运行该项目,您会看到球拍出现在场景中,并且球在击中球拍时会反弹(如果您等待的时间足够长)。请参考下图。到目前为止,一切都很好!接下来,我们想让玩家移动球拍。8.移动挡板移动挡板需要检测接触相关信息。为此,我们在GameScene类中实现了以下触摸处理方法:UITouch>,withEventevent:UIEvent?)但是,在此之前,您需要添加一个属性。转到GameScene.swift文件并将以下属性添加到类中:varisFingerOnPaddle=false此属性负责存储玩家是否点击了球拍。您将需要它来执行与拖动桨相关的操作。现在,请在GameScene.swift文件的touchesBegan(_:withEvent:)方法中添加以下代码:self)ifletbody=physicsWorld.bodyAtPoint(touchLocation){ifbody.node!.name==PaddleCategoryName{print("Begantouchonpaddle")isFingerOnPaddle=true}}}上面的代码获取触摸信息并使用它在场景。接下来,使用bodyAtPoint(_:)方法找到与该位置的节点(如果有)关联的物理Body。最后检查触摸位置是否有节点;如果是,则判断该节点是否为挡板。这是早期创建对象名称发挥作用的地方——您可以通过检查名称来检查特定对象。如果触摸位置的对象是桨,则会向控制台发送一条日志消息并将isFingerOnPaddle设置为true。现在您可以构建并重新运行该项目。当您点击挡板时,您应该会在控制台中看到一条日志消息。接下来,添加以下代码:overridefunctouchesMoved(touches:Set
