当前位置: 首页 > 科技观察

基于SpriteKit+Swift,开发一款竹子积木游戏

时间:2023-03-15 08:19:08 科技观察

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,withEventevent:UIEvent?){//1ifisFingerOnPaddle{//2lettouch=touches.firstlettouchtouchLocation=touch!.locationInNode(self)letpreviousLocation=touch!.previousLocationInNode(self)//3letpaddle=childNodeWithName(PaddleCategoryName)as!SKSpriteNode//4varpaddlepaddleX=paddle.position.x+(touchLocation.x-previousLocation.x)//5paddleX=max(paddleX,paddle.size.width/2)paddleX=min(paddleX,size.width-paddle.size.width/2)//6paddle.position=CGPoint(x:paddleX,y:paddle.position.y)}}这是paddle移动的主要逻辑。(1)检查是否有播放器在触摸边框。(2)如果是,那么就需要更新paddle的位置,当然具体的方式还是要看玩家如何移动手指。为此,您需要获取当前触摸位置和最后触摸位置。(3)获取挡板的SKSpriteNode。(4)使用当前位置加上新位置与上次触摸位置的差值,计算出bezelx坐标值。(5)在重新定位挡板之前,限制其x坐标位置,使挡板不会超出屏幕的左右两侧。(6)将挡板的位置设置为刚才计算出的位置。触摸处理剩下的唯一事情就是做一些清理,这是在方法touchesEnded(_:withEvent:)中实现的,如下所示:您将isFingerOnPaddle属性设置为false。这确保了当玩家将手指从屏幕上移开并再次点击球拍时,球拍不会跳到之前的触摸位置。完美的!现在,当您再次构建并运行该项目时,您会发现球在屏幕上弹跳,您可以使用边框来影响它的运动。现在,感觉已经很好了!9.接触到目前为止,您已经实现了基本的球弹跳和球拍运动来控制球。虽然这已经足够有趣了,但要真正成为一款游戏,您需要一种方法来控制玩家在游戏中的输赢。当球击中屏幕底部而不是球拍时,玩家应该输。但是你如何使用SpriteKit来检测这种情况呢?SpriteKit可以检测两个物理对象之间的接触。但是,为了使其正常工作,您需要按照一些步骤以特定方式进行设置。这里只作简要说明。稍后将解释每个步骤的更多细节。描述如下:设置物理体位掩码:在你的游戏中,你可能有几种不同类型的物理体——例如,你可能有玩家、敌人、子弹、奖励物品等。为了唯一标识这些不同的物理体的类型,你需要为每个物理体配置几个位掩码。这些掩码包括:categoryBitMask:此位掩码标识Body所属的类别。您可以使用类来定义Body如何与其他Body交互。CategoryBitMask是一个32位整数,其中每一位代表一个类别。因此,您最多可以在游戏中使用32个自定义类别。这应该足以让大多数游戏为每个对象类型设置一个单独的类。对于更复杂的游戏,请记住每个Body可以属于多个类别的技巧。因此,通过精心设计类别,您甚至可以克服32个类别的限制。contactTestBitMask:设置此掩码中的位使SpriteKit能够在Body接触分配给该特定类的另一个Body时通知委托。默认情况下,所有位都被清除-您不会收到对象之间的任何联系的通知。为了获得最佳性能,您应该只设置您真正有兴趣与之交互的联系人掩码。collisionBitMask:使用此掩码,您可以定义哪些物体可以与当前物理物体发生碰撞。例如,当一个非常重的物体遇到一个更轻的物体时,您可以使用此技术来避免碰撞计算,因为这只会对重物体的速度产生可忽略不计的变化。但是,您也可以使用它让两个物体相互穿透。设置并实现接触委托(delegate,有的译为“代理”):接触委托实际上是SKPhysicsWorld的一个属性。当使用contactTestBitMasks的两个主体开始和完成碰撞时,会通知此委托。十、三、二、一接触算法首先,让我们创建描述不同类的常量。为此,只需将以下常量定义添加到GameScene.swift文件中:letBallCategory:UInt32=0x1<<0letBottomCategory:UInt32=0x1<<1letBlockCategory:UInt32=0x1<<2letPaddleCategory:UInt32=0x1<<3letBorderCategory:UInt32=0x1<<4上面定义了五个类别。这里使用的方法是将最后一位设置为1,将所有其他位设置为零。然后使用<<运算符将位向左移动。因此,每个类别常量只有一位被设置为1,并且这个1在二进制数中的位置对于上述四个类别是唯一的。现在,你只需要上面的类来描述屏幕和球;但是,您还应该使用其他方式来解释游戏运行的逻辑。一旦建立了上述常量,就可以创建横跨屏幕底部的实体了。[建议]读者尝试结合本文前面介绍的原理,用自己的方法来解决围绕屏幕边缘创建障碍的相关问题。现在,让我们讨论如何创建对编程核心问题的曝光。首先,通过将以下代码添加到didMoveToView(_:)方法来为游戏对象设置categoryBitMasks掩码:.categoryBitMask=PaddleCategoryborderBody.categoryBitMask=BorderCategory此代码只是将之前创建的常量分配给相应物理Body的categoryBitMask掩码。现在,通过将以下代码行添加到didMoveToView(_:)方法来设置contactTestBitMask掩码:ball.physicsBody!.contactTestBitMask=BottomCategory现在,您只想在球触及屏幕底部时收到通知。接下来,让我们在GameScene类中为所有物理接触创建委托。为此,只需更改以下行:classGameScene:SKScene{更改为以下形式:classGameScene:SKScene,SKPhysicsContactDelegate{这很好:GameScene的标识现在是SKPhysicsContactDelegate(因为它符合SKPhysicsContactDelegate协议),它将接收all已配置物理体的碰撞通知。现在您需要将GameScene设置为physicsWorld中的委托。因此,将以下代码行添加到方法didMoveToView(_:)中,就在语句physicsWorld.gravity=CGVector(dx:0.0,dy:0.0)的下方:physicsWorld.contactDelegate=self最后,您需要执行didBeginContact(_:)来处理碰撞。为此,只需将以下方法添加到GameScene.swift中:}else{firstBody=contact.bodyBsecondBody=contact.bodyA}//3iffirstBody.categoryBitMask==BallCategory&&secondbody.categoryBitMask==BottomCategory{print("Hitbottom.Firstcontacthasbeenmade.")}}让我们分析一下上面的方法:(1)创建两个局部变量,用于存储碰撞中涉及的两个物理体。(2)检查两次碰撞的物理体,看哪一个使用了较低类别的位掩码掩码。然后,将它们存储到局部变量中;这样下层对应的Body就一直保存在firstBody变量中。这将在分析特定类之间的联系时为您节省很多精力。(3)得益于之前实现的排序操作,现在只需要检查firstBody是否属于BallCategory类,secondBody是否属于BottomCategory类,即可判断小球是否触及屏幕底部——如您已经知道,如果firstBody属于类BottomCategory,则secondBody不能属于BallCategory类(因为BottomCategory的位掩码高于BallCategory)。在这个例子中,我们只输出一条简单的日志消息。现在,请再次构建并运行您的游戏。如果一切顺利,只要球未击中球拍并击中屏幕底部,您应该会在控制台中看到日志消息。就像下图:okay!现在最困难的部分已经完成。最后,剩下的就是添加竹子块和游戏逻辑,您将在本系列的下一部分中了解这些内容。基于SpriteKit+Swift开发竹子积木游戏(下)