在本系列中,我们将学习如何使用SpriteKit来为iOS构建2D游戏。 在本文中,我们将学习SpriteKit的两个重要功能:动作和物理。
要继续学习本教程,只需下载随附的GitHub repo即可 。 它有两个文件夹:一个用于动作,一个用于物理。 只需在Xcode中打开任何一个入门项目,就可以完成。
动作
对于大多数游戏,您将希望节点执行诸如移动,缩放或旋转之类的操作。 SKAction
类的设计就是SKAction
这一目的。 SKAction
类具有许多类方法,可在一段时间内调用这些类方法来移动,缩放或旋转节点的属性。
您还可以使用SKAction
类播放声音,为一组纹理设置动画或运行自定义代码。 您可以运行一个动作,依次运行两个或多个动作,作为一个组同时运行两个或多个动作,甚至重复任何动作。
运动
让我们在屏幕上移动一个节点。 在Example1.swift中输入以下内容。
class Example1: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 200, y: player.size.height)
addChild(player)
let movePlayerAction = SKAction.moveTo(y: 900, duration: 2)
player.run(movePlayerAction)
}
}
在这里,我们创建一个SKAction
并调用类方法moveTo(y:duration:)
,该方法以将节点移动到的y
位置和duration
以秒为单位)作为参数。 要执行该动作,必须调用节点的run(_:)
方法并传递SKAction
。 如果现在进行测试,您应该会看到一架飞机在屏幕上移动。
移动方法有多种,其中包括move(to:duration:)
将节点移动到x和y轴上的新位置,以及move(by:duration:)
将节点移动到相对位置。到当前位置。 我建议您通读SKAction
上的文档,以了解所有不同的move方法。
完成关闭
run方法的另一种形式是允许您在完成闭包中调用某些代码。 在Example2.swift中输入以下代码。
class Example2: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 200, y: player.size.height)
addChild(player)
let movePlayerAction = SKAction.moveTo(y: 900, duration: 2)
player.run(movePlayerAction, completion: {
print("Player Finished Moving")
})
}
}
一旦动作完全完成执行, run(_:completion:)
方法允许您运行代码块。 在这里,我们执行一个简单的print语句,但是代码可能会像您需要的那样复杂。
动作顺序
有时,您需要一个接一个地运行操作,您可以使用sequence(_:)
方法执行此操作。 将以下内容添加到Example3.swift中 。
class Example3: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let movePlayerAction = SKAction.moveTo(y: 900, duration: 2)
let scalePlayerAction = SKAction.scale(to: 3, duration: 2)
let sequenceAction = SKAction.sequence([movePlayerAction, scalePlayerAction])
player.run(sequenceAction)
}
}
在这里,我们创建两个SKAction
:一个使用moveTo(y:duration:)
,另一个使用scale(to:duration:)
,它更改节点的x和y比例。 然后,我们调用sequence(_:)
方法,该方法将一个要SKAction
运行的SKAction
数组作为参数。 如果现在进行测试,您应该会看到飞机在屏幕上移动,到达目的地后,飞机将成长为原始尺寸的三倍。
分组动作
在其他时候,您可能希望将操作作为一个组一起运行。 将以下代码添加到Example4.swift中 。
class Example4: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let movePlayerAction = SKAction.moveTo(y: 900, duration: 2)
let scalePlayerAction = SKAction.scale(to: 3, duration: 2)
let groupAction = SKAction.group([movePlayerAction, scalePlayerAction])
player.run(groupAction)
}
}
在这里,我们使用与上一个示例相同的moveTo
和scale
方法,但是我们还要调用group(_:)
方法,该方法将同时运行的SKAction
数组作为参数。 如果现在要测试,您会看到飞机同时移动和缩放。
反转动作
通过调用reversed()
方法可以撤消其中一些操作。 确定哪些操作支持reversed()
方法的最佳方法是查阅文档。 可逆的动作之一是fadeOut(withDuration:)
,它可以通过更改其alpha值使节点淡化为不可见。 让我们先使飞机淡出然后再淡入。将以下内容添加到Example5.swift中 。
class Example5: SKScene {
override func didMove(to view: SKView) {
let player = SKSpriteNode(imageNamed: "player")
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let fadePlayerAction = SKAction.fadeOut(withDuration: 2)
let fadePlayerActionReversed = fadePlayerAction.reversed()
let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed])
player.run(fadePlayerSequence)
}
}
在这里,我们创建一个SKAction
并调用fadeOut(withDuration:)
方法。 在下一行代码中,我们调用reversed()
方法,这将导致该操作反转其刚完成的操作。 测试项目,您将看到飞机逐渐消失,然后逐渐消失。
重复动作
如果您需要重复执行特定的操作次数,则可以使用repeat(_:count:)
和repeatForever(_:)
方法。 让我们反复使飞机逐渐消失,然后再永久返回。 在Example6.swift中输入以下代码。
class Example6: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let fadePlayerAction = SKAction.fadeOut(withDuration: 2)
let fadePlayerActionReversed = fadePlayerAction.reversed()
let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed])
let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence)
player.run(fadeOutInRepeatAction)
}
}
在这里,我们调用了repeatForever(_:)
方法,并传入了fadePlayerSequence
。 如果进行测试,您会看到飞机逐渐褪色,然后永远恢复原状。
停止动作
很多时候,您需要停止节点运行其操作。 您可以为此使用removeAllActions()
方法。 让我们在触摸屏幕时使播放器节点停止褪色。 在Example7.swift中添加以下内容。
class Example7: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let fadePlayerAction = SKAction.fadeOut(withDuration: 2)
let fadePlayerActionReversed = fadePlayerAction.reversed()
let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed])
let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence)
player.run(fadeOutInRepeatAction)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
player.removeAllActions()
}
}
如果您触摸屏幕,则播放器节点将删除所有动作,并且不再淡入和淡出。
跟踪动作
有时,您需要一种跟踪行为的方法。 例如,如果您在一个节点上运行两个或多个操作,则可能需要一种识别它们的方法。 您可以通过在节点上注册一个密钥来完成此操作,该密钥是一个简单的文本字符串。 在Example8.swift中输入以下内容。
class Example8: SKScene {
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
player.position = CGPoint(x: 100, y: player.size.height)
addChild(player)
let fadePlayerAction = SKAction.fadeOut(withDuration: 2)
let fadePlayerActionReversed = fadePlayerAction.reversed()
let fadePlayerSequence = SKAction.sequence([fadePlayerAction, fadePlayerActionReversed])
let fadeOutInRepeatAction = SKAction.repeatForever(fadePlayerSequence)
player.run(fadeOutInRepeatAction, withKey: "faderepeataction")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if player.action(forKey: "faderepeataction") != nil {
player.removeAction(forKey: "faderepeataction")
}
}
}
在这里,我们正在调用节点的run(_:withKey:)
方法,如上所述,该方法需要一个简单的文本字符串。 在touchesBegan(_:with:)
方法中,我们正在调用action(forKey:)
以确保节点具有我们分配的键。 如果是这样,我们将调用.removeAction(forKey:)
,它将您先前设置的密钥作为参数。
声音动作
很多时候,您会想在游戏中播放一些声音。 您可以使用类方法playSoundFileNamed(_:waitForCompletion:)
完成此操作。 在Example9.swift中输入以下内容。
class Example9: SKScene {
override func didMove(to view: SKView) {
let planeSound = SKAction.playSoundFileNamed("planesound", waitForCompletion: false)
run(planeSound)
}
}
playSoundFileNamed(_:waitForCompletion:)
以不带扩展名的声音文件的名称和布尔值作为参数,该布尔值确定操作是否要等到声音完成后再继续。
例如,假设您有一个序列中的两个动作,声音是第一个动作。 如果waitForCompletion
为true
则序列将一直等到声音播放完毕,然后再移至序列中的下一个动作。 如果您需要对声音的更多控制,可以使用SKAudioNode
。 我们不会在本系列SKAudioNode
中介绍SKAudioNode
,但绝对是您在SpriteKit开发人员职业生涯中应注意的事项。
框架动画
许多游戏都需要对一组图像进行动画处理。 在这些情况下,您可以使用animate(with:timePerFrame:)
。 在Example10.swift中输入以下内容。
class Example10: SKScene {
let explosion = SKSpriteNode(imageNamed: "explosion1")
override func didMove(to view: SKView) {
addChild(explosion)
var explosionTextures:[SKTexture] = []
for i in 1...6 {
explosionTextures.append(SKTexture(imageNamed: "explosion\(i)"))
}
let explosionAnimation = SKAction.animate(with: explosionTextures,
timePerFrame: 0.3)
explosion.run(explosionAnimation)
}
}
animate(with:timePerFrame:)
将一个SKTexture
数组和一个timePerFrame
值作为参数,该值将是timePerFrame
纹理更改之间所花费的时间。 要执行此操作,请调用节点的run方法并传递SKAction
。
自定义代码动作
我们将要看到的最后一种操作是使您运行自定义代码的操作。 当您需要在操作中间执行某些操作时,或者仅需要一种执行SKAction
类未提供的操作的方法时,这可能会派上用场。 在Example11.swift中输入以下内容。
class Example11: SKScene {
override func didMove(to view: SKView) {
let runAction = SKAction.run(printToConsole)
run(runAction)
}
func printToConsole(){
print("SKAction.run() invoked")
}
}
在这里,我们调用场景的run(_:)
方法并传递一个函数printToConsole()
作为参数。 请记住,场景也是节点,因此您也可以在它们上调用run(_:)
方法。
到此结束我们对动作的研究。 SKAction
类可以做很多事情,我建议阅读本教程后,您可以进一步探索SKActions
文档 。
物理
SpriteKit提供了一个强大的物理引擎,无需任何设置。 首先,只需向每个节点添加一个物理实体,您就可以开始了。 物理引擎基于流行的Box2d引擎构建。 但是,SpriteKit的API比原始的Box2d API更易于使用。
让我们从向节点添加物理物体开始,看看会发生什么。 将以下代码添加到Example1.swift中 。
...
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
...
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.affectedByGravity = false
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
player.physicsBody?.affectedByGravity = true
}
继续并立即测试项目。 您将看到飞机坐在场景的顶部。 在屏幕上按一下后,飞机将从屏幕上掉落,并且将永远坠落。 这显示了开始使用物理学的过程很简单-只需向节点添加物理学实体,就可以完成工作。
physicsBody
形状
physicsBody
属性的类型为SKPhysicsBody
,这将是节点形状的粗略轮廓...或节点形状的非常精确轮廓,具体取决于用于初始化此属性的构造方法。
在这里,我们使用了init(circleOfRadius:)
初始化程序,该初始化程序将圆的半径作为参数。 还有其他几种初始化程序,包括一个用于CGPath的矩形或多边形的初始化程序。 您甚至可以使用节点自己的纹理,这将使physicsBody
几乎完全代表该节点。
若要了解我的意思,请使用以下代码更新GameViewController.swift文件。 我评论了要添加的行。
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:CGSize(width: 768, height: 1024))
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = false
skView.showsPhysics = true // THIS LINE ADDED
scene.scaleMode = .aspectFill
skView.presentScene(scene)
}
现在,节点的physicsBody
将以绿色概述。 在碰撞检测中,将评估physicsBody
的形状。 此示例将在围绕平面的圆圈引导碰撞检测,这意味着,例如,如果子弹撞击圆圈的外边缘,则将其计为碰撞。
![圈身](https://cms-assets.tutsplus.com/uploads/users/211/posts/28961/image/circleBody.jpg)
现在将以下内容添加到Example2.swift中 。
override func didMove(to view: SKView) {
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.affectedByGravity = false
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
}
在这里,我们使用精灵的纹理。 如果现在测试项目,应该会看到轮廓已更改为接近精灵图形的纹理。
重力
在前面的示例中,我们将physicsBody
的affectedByGravity
physicsBody
属性设置为false
。 将物理实体添加到节点后,物理引擎将接管。 结果是在运行项目时飞机立即掉落!
您也可以像这里一样在每个节点的基础上设置重力,也可以完全关闭重力。 将以下内容添加到Example3.swift中 。
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
...
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
physicsWorld.gravity = CGVector(dx:0 , dy: -9.8)
}
我们可以使用physicsWorld
gravity
属性设置gravity
。 gravity
属性的类型为CGVector
。 我们将dx
和dy
分量都设置为0 ,然后在触摸屏幕时,将dy
属性设置为-9.8 。 分量以米为单位,默认值为( 0,-9.8 ),它表示地球的重力。
边环
就目前而言,添加到场景中的任何节点都将永远掉出屏幕。 我们可以使用init(edgeLoopFrom:)
方法在场景周围添加边缘循环。 将以下内容添加到Example4.swift中 。
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
...
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
physicsWorld.gravity = CGVector(dx:0 , dy: -9.8)
}
在这里,我们为场景本身添加了一个物理实体。 init(edgeLoopFrom:)
将定义其边缘的CGRect
作为参数。 如果您现在进行测试,您会发现飞机仍然掉落; 但是,它与此边缘循环交互,不再掉出场景。 它还会弹起,甚至在其侧面稍微转弯。 这就是物理引擎的力量-您可以免费获得所有这些功能。 自己写这样的东西会很复杂。
弹跳
我们已经看到飞机反弹并在其侧面转弯。 您可以控制弹跳以及物理物体是否允许旋转。 在Example5.swift中输入以下内容 。
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
...
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.restitution = 0.8
player.physicsBody?.allowsRotation = false
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
physicsWorld.gravity = CGVector(dx:0 , dy: -9.8)
}
如果您现在进行测试,您会发现该player
非常有弹性,需要花费几秒钟才能安定下来。 您还将注意到它不再旋转。 restitution
属性的取值范围是0.0 (弹性较小)到1.0 (弹性很大),而allowsRotation
属性是一个简单的布尔值。
摩擦
在现实世界中,当两个物体彼此相对运动时,它们之间会有些摩擦。 您可以更改物理物体的摩擦力,这等于物体的“粗糙度”。 此属性必须在0.0和1.0之间。 默认值为0.2 。 将以下内容添加到Example6.swift中 。
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
…
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.allowsRotation = false
player.physicsBody?.restitution = 0.0
player.name = "player"
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
let rectangle = SKSpriteNode(color: .blue, size: CGSize(width: size.width, height: 20))
rectangle.physicsBody = SKPhysicsBody(rectangleOf: rectangle.size)
rectangle.physicsBody?.isDynamic = false
rectangle.zRotation = 3.14 * 220 / 180
rectangle.physicsBody?.friction = 0.0
rectangle.position = CGPoint(x: size.width/2, y: size.width/2)
addChild(rectangle)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
physicsWorld.gravity = CGVector(dx:0 , dy: -9.8)
}
在这里,我们创建一个矩形Sprite并将其physicsBody
的friction
属性设置为0.0 。 如果现在进行测试,您将看到飞机非常Swift地向下滑动旋转的矩形。 现在将摩擦特性更改为1.0并再次测试。 您会看到飞机并没有以那么快的速度滑落到矩形上。 这是由于摩擦。 如果您希望它移动得更慢,则可以对player
的physicsBody
施加更大的摩擦力(请记住默认值为0.2 )。
密度和质量
您还可以在物理物体上更改其他几个属性,例如密度和质量。 density
和mass
属性是相互关联的,当您更改其中一个时,另一个将自动重新计算。 首次创建物理物体时,将计算该物体的area
属性,并且此后再也不会更改(只读)。 密度和质量基于mass = density * area
公式。
当场景中有多个节点时,密度和质量会影响节点之间如何弹起并相互作用的模拟。 考虑一下篮球和保龄球,它们的大小大致相同,但是保龄球的密度要大得多。 当它们碰撞时,篮球将比保龄球改变方向和速度。
力与冲动
您可以施加力和脉冲来移动物理物体。 立即施加一次脉冲,并且仅施加一次。 另一方面,通常施加力以产生连续效果。 从添加力的时间开始施加力,直到处理下一帧模拟为止。 要施加连续的力,您需要在每个框架上施加力。 将以下内容添加到Example7.swift中 。
let player = SKSpriteNode(imageNamed: "enemy1")
override func didMove(to view: SKView) {
...
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.allowsRotation = false
player.name = "player"
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
...
physicsWorld.gravity = CGVector(dx:0 , dy: -9.8)
if(touchedNode.name == "player"){
player.physicsBody?.applyImpulse(CGVector(dx:0 , dy: 100))
}
}
运行项目,等待播放器停在屏幕底部,然后点击播放器。 您将看到播放器在屏幕上飞来,并最终再次停在底部。 我们应用使用该方法的脉冲applyImpulse(_:)
,这需要作为参数CGVector
和牛顿-秒进行测量。
为什么不尝试相反的操作并向玩家节点添加力量? 请记住,您将需要连续施加力才能达到理想的效果。 做到这一点的一个好地方是场景的update(_:)
方法。 另外,您可能想尝试增加播放器上的恢复属性,以了解它如何影响模拟。
override func update(_ currentTime: TimeInterval) {
//APPLY FORCE HERE
}
碰撞检测
物理引擎具有强大的碰撞和接触检测系统。 默认情况下,具有物理物体的任何两个节点都可以碰撞。 您在前面的示例中已经看到了这一点-不需要特殊的代码即可告诉对象进行交互。 但是,可以通过在物理主体上设置“类别”来更改此行为。 然后,此类别可用于确定哪些节点将相互碰撞,也可用于在某些节点建立联系时通知您。
接触与碰撞之间的区别在于,接触用于判断两个物理物体何时相互接触。 另一方面,碰撞阻止了两个物理物体相互进入空间—当物理引擎检测到碰撞时,它将施加相反的脉冲以使物体再次分离。 从前面的示例中,我们已经看到了与播放器和边缘循环以及播放器和矩形的碰撞。
physicsBodies
类型
在继续建立物理机构的类别之前,我们应该讨论physicsBodies
的类型。 有三种:
- 动态体积模拟具有体积和质量的对象。 这些对象受物理世界中的力和碰撞影响(例如,前面示例中的飞机)。
- 静态体积不受力和碰撞的影响。 但是,由于它本身具有体积,因此其他物体可以反弹并与其相互作用。 您可以将物理
isDynamic
的isDynamic
属性设置为false以创建静态体积。 这些体积永远不会被物理引擎移动。 我们在前面的示例6中看到了这一点,其中飞机与矩形相互作用,但是矩形不受平面或重力的影响。 要了解我的意思,请返回示例六,并删除设置了rectangle.physicsBody?.isDynamic = false
的代码行。 - 第三类物理物体是edge ,它是静态的无体积物体。 在前面的所有示例中,我们都已经在场景周围创建了边缘循环,从而看到了这种类型的主体。 边与其他基于体积的实体交互,但永远不会与其他边交互。
类别使用具有32 个单独标志的32位整数,这些标志可以打开或关闭。 这也意味着您最多只能有32个类别。 对于大多数游戏来说,这应该不会出现问题,但是要牢记这一点。
创建类别
通过转到文件 > 新建 > 文件 ,并确保斯威夫特文件被突出显示创建一个新雨燕文件。
输入PhysicsCategories作为名称,然后按Create 。
在刚刚创建的文件中输入以下内容。
struct PhysicsCategories {
static let Player : UInt32 = 0x1 << 0
static let EdgeLoop : UInt32 = 0x1 << 1
static let Redball : UInt32 = 0x1 << 2
}
我们使用PhysicsCategories
结构为Player
, EdgeLoop
和RedBall
创建类别。 我们正在使用移位将位打开。
现在,在Example8.swift中输入以下内容。
...
let player = SKSpriteNode(imageNamed: "enemy1")
var dx = -20
var dy = -20
override func didMove(to view: SKView) {
player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.size)
player.physicsBody?.allowsRotation = false
player.physicsBody?.restitution = 0.0
player.physicsBody?.categoryBitMask = PhysicsCategories.Player
player.physicsBody?.contactTestBitMask = PhysicsCategories.RedBall
player.physicsBody?.collisionBitMask = PhysicsCategories.EdgeLoop
player.position = CGPoint(x: size.width/2 , y: size.height - player.size.height)
addChild(player)
player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy))
let redBall = SKShapeNode(circleOfRadius: 100)
redBall.fillColor = SKColor.red
redBall.position = CGPoint(x: size.width/2, y: size.height/2)
redBall.physicsBody = SKPhysicsBody(circleOfRadius: 100)
redBall.physicsBody?.isDynamic = false
redBall.physicsBody?.categoryBitMask = PhysicsCategories.RedBall
addChild(redBall)
physicsWorld.gravity = CGVector(dx:0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsWorld.contactDelegate = self
physicsBody?.categoryBitMask = PhysicsCategories.EdgeLoop
physicsBody?.contactTestBitMask = PhysicsCategories.Player
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask){
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.RedBall != 0)){
print("Player and RedBall Contact")
}
if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EdgeLoop != 0)){
print ("Player and EdgeLoop Contact")
dx *= -1
dy *= -1
player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy))
}
}
在这里,我们像往常一样创建player
,并创建两个变量dx
和dy
,当我们向player
施加脉冲时,它们将用作CGVector
的组件。
在didMove(to:)
内部,设置播放器并添加categoryBitMask
, contactBitMask
和collisionBitMask
contactBitMask
。 categoryBitMask
应该有意义-这是播放器,因此我们将其设置为PhysicsCategories.Player
。 我们对玩家何时与redBall
接触redBall
,因此我们将contactBitMask
设置为PhysicsCategories.RedBall
。 最后,我们希望它与碰撞,并通过与边缘环物理学会受到影响,所以我们设置了collisionBitMask
到PhysicsCategories.EdgeLoop
。 最后,我们施加一个推动力。
在redBall
,我们仅将其设置为categoryBitMask
。 使用edgeLoop
,我们将其设置为categoryBitMask
,并且由于我们对player
与其接触的时间感兴趣,因此将其设置为contactBitMask
。
设置contactBitMask
和collisionBitMask
contactBitMask
,仅其中一个contactBitMask
需要引用另一个。 换句话说,您无需将两个实体都设置为彼此接触或碰撞。
对于edgeLoop
,我们将其设置为与玩家联系。 但是,我们可以改为使用按位或( |
)运算符将播放器设置为与edgeLoop
交互。 使用此运算符,可以设置多个接触或碰撞位掩码。 例如:
player.physicsBody?.contactTestBitMask = PhysicsCategories.RedBall | PhysicsCategories.EdgeLoop
为了能够在两个物体接触时做出响应,您必须实现SKPhysicsContactDelegate
协议。 您可能已经在示例代码中注意到了这一点。
class Example8: SKScene,SKPhysicsContactDelegate{
…
physicsWorld.contactDelegate = self
...
要响应联系人事件,可以实现didBegin(_:)
和didEnd(_:)
方法。 当两个对象开始接触时和它们结束接触时,将分别调用它们。 在本教程中,我们将坚持使用didBegin(_:)
方法。
这再次是didBegin(_:)
方法的代码。
func didBegin(_ contact: SKPhysicsContact) {
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if(contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask){
firstBody = contact.bodyA
secondBody = contact.bodyB
}else{
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.RedBall != 0)){
print("Player and RedBall Contact")
}
if((firstBody.categoryBitMask & PhysicsCategories.Player != 0) && (secondBody.categoryBitMask & PhysicsCategories.EdgeLoop != 0)){
print ("Player and EdgeLoop Contact")
dx *= -1
dy *= -1
player.physicsBody?.applyImpulse(CGVector(dx: dx, dy: dy))
}
}
首先,我们设置两个变量firstBody
和secondBody
。 contact参数中的两个主体没有按保证的顺序传递,因此我们将使用if语句确定哪个主体具有较低的contactBitMask
并将其设置为firstBody
。
现在,我们可以检查并查看哪些物理物体正在建立联系。 我们通过使用先前设置的PhysicsCategory
将与该对象的categoryBitMask
相乘( &&
),以查看要处理的是哪个物理对象,如果结果为非零,则说明我们拥有正确的对象。
最后,我们打印哪些物体正在建立联系。 如果它是player和edgeLoop
,那么我们还反转dx
和dy
属性,并向该播放器施加一个脉冲。 这使玩家不断移动。
总结我们对SpriteKit物理引擎的研究。 例如, SKPhysicsJoint等很多未涵盖的内容。 物理引擎非常强大,我强烈建议您从SKPhysicBody开始阅读它的所有各个方面。
结论
在这篇文章中,我们了解了动作和物理学-SpriteKit框架的两个非常重要的部分。 我们看了很多例子,但是您仍然可以在动作和物理上做很多事情,并且文档是一个学习的好地方。
在本系列的下一个也是最后一部分,我们将通过编写一个简单的游戏来总结我们学到的一切。 感谢您的阅读,我在那里见!
翻译自: https://code.tutsplus.com/tutorials/spritekit-actions-and-physics--cms-28961