SpriteKit基础知识:动作和物理

在本系列中,我们将学习如何使用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)
    }
}

在这里,我们使用与上一个示例相同的moveToscale方法,但是我们还要调用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:)以不带扩展名的声音文件的名称和布尔值作为参数,该布尔值确定操作是否要等到声音完成后再继续。

例如,假设您有一个序列中的两个动作,声音是第一个动作。 如果waitForCompletiontrue则序列将一直等到声音播放完毕,然后再移至序列中的下一个动作。 如果您需要对声音的更多控制,可以使用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的形状。 此示例将在围绕平面的圆圈引导碰撞检测,这意味着,例如,如果子弹撞击圆圈的外边缘,则将其计为碰撞。

圈身

现在将以下内容添加到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)
}

在这里,我们使用精灵的纹理。 如果现在测试项目,应该会看到轮廓已更改为接近精灵图形的纹理。

纹理体

重力

在前面的示例中,我们将physicsBodyaffectedByGravity 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属性设置gravitygravity属性的类型为CGVector 。 我们将dxdy分量都设置为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.01.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并将其physicsBodyfriction属性设置为0.0 。 如果现在进行测试,您将看到飞机非常Swift地向下滑动旋转的矩形。 现在将摩擦特性更改为1.0并再次测试。 您会看到飞机并没有以那么快的速度滑落到矩形上。 这是由于摩擦。 如果您希望它移动得更慢,则可以对playerphysicsBody施加更大的摩擦力(请记住默认值为0.2 )。

密度和质量

您还可以在物理物体上更改其他几个属性,例如密度和质量。 densitymass属性是相互关联的,当您更改其中一个时,另一个将自动重新计算。 首次创建物理物体时,将计算该物体的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的类型。 有三种:

  1. 动态体积模拟具有体积和质量的对象。 这些对象受物理世界中的力和碰撞影响(例如,前面示例中的飞机)。
  2. 静态体积不受力和碰撞的影响。 但是,由于它本身具有体积,因此其他物体可以反弹并与其相互作用。 您可以将物理isDynamicisDynamic属性设置为false以创建静态体积。 这些体积永远不会被物理引擎移动。 我们在前面的示例6中看到了这一点,其中飞机与矩形相互作用,但是矩形不受平面或重力的影响。 要了解我的意思,请返回示例六,并删除设置了rectangle.physicsBody?.isDynamic = false的代码行。
  3. 第三类物理物体是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结构为PlayerEdgeLoopRedBall创建类别。 我们正在使用移位将位打开。

现在,在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 ,并创建两个变量dxdy ,当我们向player施加脉冲时,它们将用作CGVector的组件。

didMove(to:)内部,设置播放器并添加categoryBitMaskcontactBitMaskcollisionBitMask contactBitMaskcategoryBitMask应该有意义-这是播放器,因此我们将其设置为PhysicsCategories.Player 。 我们对玩家何时与redBall接触redBall ,因此我们将contactBitMask设置为PhysicsCategories.RedBall 。 最后,我们希望它与碰撞,并通过与边缘环物理学会受到影响,所以我们设置了collisionBitMaskPhysicsCategories.EdgeLoop 。 最后,我们施加一个推动力。

redBall ,我们仅将其设置为categoryBitMask 。 使用edgeLoop ,我们将其设置为categoryBitMask ,并且由于我们对player与其接触的时间感兴趣,因此将其设置为contactBitMask

设置contactBitMaskcollisionBitMask 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))
    }
}

首先,我们设置两个变量firstBodysecondBody 。 contact参数中的两个主体没有按保证的顺序传递,因此我们将使用if语句确定哪个主体具有较低的contactBitMask并将其设置为firstBody

现在,我们可以检查并查看哪些物理物体正在建立联系。 我们通过使用先前设置的PhysicsCategory将与该对象的categoryBitMask相乘( && ),以查看要处理的是哪个物理对象,如果结果为非零,则说明我们拥有正确的对象。

最后,我们打印哪些物体正在建立联系。 如果它是player和edgeLoop ,那么我们还反转dxdy属性,并向该播放器施加一个脉冲。 这使玩家不断移动。

总结我们对SpriteKit物理引擎的研究。 例如, SKPhysicsJoint等很多未涵盖的内容。 物理引擎非常强大,我强烈建议您从SKPhysicBody开始阅读它的所有各个方面。

结论

在这篇文章中,我们了解了动作和物理学-SpriteKit框架的两个非常重要的部分。 我们看了很多例子,但是您仍然可以在动作和物理上做很多事情,并且文档是一个学习的好地方。

在本系列的下一个也是最后一部分,我们将通过编写一个简单的游戏来总结我们学到的一切。 感谢您的阅读,我在那里见!

翻译自: https://code.tutsplus.com/tutorials/spritekit-actions-and-physics--cms-28961

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值