周四. 10月 21st, 2021

物理实体概述

当场景中存在多个节点时,这些节点经常需要交互,相互碰撞,并在过程中传递速度变化,模拟重力和其他力的作用。为此,创建物理实体(SKPhysicsBody)并将其附加到场景中的节点(SKNode),作为节点具备的物理实体。本文根据apple官方文档整理了以下内容:

SpriteKit使用国际单位制:米-千克-秒制

来自官方文档

配置自定义物理实体

物理实体使用其节点的位置和方向将自己放置在模拟中。每个物理体都有其他的特性来定义模拟是如何在其上运行的。这些包括物理物体的固有特性,比如它的质量和密度,以及施加在它身上的特性,比如它的速度。这些特性定义了物体的运动方式、在模拟中受力的影响以及物体对与其他物理物体碰撞的反应。

碰撞与力作用

每次场景计算新的动画帧时,它都会模拟连接到节点树的物理实体上的力和碰撞的效果。它计算每个物理体的最终位置、方向和速度。然后,场景更新每个相应节点的位置和旋转。通过向场景中添加SKFieldNode对象,可以将其他力自动应用于多个物理实体。也可以通过直接修改特定场体的速度或直接对其应用力或脉冲来直接影响它。计算每个物体的加速度和速度,物体相互碰撞。然后,在仿真完成后,更新相应节点的位置和旋转。

控制交互

物理效应相互作用可以得到精确控制。例如,可以指定特定的物理场节点仅影响场景中物理实体的子集。您还可以决定哪些物理实体可以相互碰撞,并分别决定哪些交互导致您的应用程序被调用。您可以使用这些回调来添加游戏逻辑。例如,当一个节点的物理实体被另一个物理实体击中时,您的游戏可能会破坏该节点。

代码举例

物理世界

如果希望在场景中加入物理方面的交互和各种力作用,需要在声明场景类的时候继承SKPhysicsContactDelegate,并且将协议的实现方设置为这个场景类,必要的代码如下:

class GameScene: SKScene, SKPhysicsContactDelegate {
    override func didMove(to view: SKView) {
        self.physicsWorld.contactDelegate = self
    }
}

self.physicsWorld是SKPhysicsWorld类的对象,可以修改物理世界的重力.gravity,修改物理世界模拟的速度.speed。有了物理世界之后,创建物理实体才会符合物理规律。

初始化物理体

物理实体需要依附于Sprite节点,举个例子,我有一张player.png作为玩家形象,以这张图片初始化玩家节点对象SKSpriteNode,然后以图片纹理创建物理实体(图片需要包含alpha通道)

var playerNode = SKSpriteNode.init(imageNamed: "player") // 创建节点
let texture = SKTexture(imageNamed: "player")    
playerNode.physicsBody = SKPhysicsBody(texture: texture, size: texture.size()) // 创建物理体

物理体的常用特性

创建节点对应的物理实体后,所有物理实体参数都是默认值,下面列举一些常用的物理实体的参数,并描述这些参数的含义:

1. 决定力对物理实体的影响

playerNode.physicsBody?.affectedByGravity = true //是否受重力影响
playerNode.physicsBody?.allowsRotation = true // 是否允许旋转
playerNode.physicsBody?.isDynamic = true // 物理实体经过物理模拟后是否移动

2. 物理实体的属性

var mass: CGFloat // 物理实体的重力(kg)
var density: CGFloat // 密度(kg/m3)
var area: CGFloat // The area covered by the body.
var friction: CGFloat // 摩擦系数,越大摩擦力越大 
var restitution: CGFloat // 弹性系数,越大撞击后反弹距离越远

3. 碰撞检测相关属性

var categoryBitMask: UInt32 // 定义物理实体类别的掩码,默认值为0xffffffff
var collisionBitMask: UInt32 // 定义物理实体可以与哪些类别发生碰撞,默认值为0xffffffff
var usesPreciseCollisionDetection: Bool // 物理实体碰撞检测是否采用迭代计算(更精确)
var contactTestBitMask: UInt32 // 定义物理实体与哪些类别发生碰撞会通知contact代理,默认值为0x00000000
func allContactedBodies() -> [SKPhysicsBody] // 函数:返回与它接触的所有物理实体数组

碰撞检测是比较重要的内容,categoryBitMask掩码一共包括32个分类,比如有玩家掩码和地面掩码,所有玩家的物理实体全部赋值为玩家掩码,所有地面的物理实体都是地面掩码,定义如下:

let floorCategory: UInt32 = 0x00000001 << 0
let playerCategory: UInt32 = 0x00000001 << 1
playerNode.physicsBody?.categoryBitMask = playerCategory
floorNode.physicsBody?.categoryBitMask = floorCategory

判断其他物体是否可以跟自己发生碰撞,要根据自己的collisionBitMask的值与对方物体的categoryBitMask的值进行逻辑与运算,得到的结果不为0,即可以发生碰撞

判断碰撞的两个物体是否发生影响时,假设为A和B,首先是A的categoryBitMask与B的contactTestBitMask进行逻辑与运算,B也做同样的运算,当两者的结果都不为0时,两者即可在碰撞的时候互相影响

简言之,collisionBitMask只需将可以与物理实体发生碰撞的掩码类别做或运算,contactTestBitMask只需将发生碰撞会通知contact代理的掩码类别做或运算,举例如下,玩家可以与石头和地板发生碰撞:

let stoneCategory: UInt32 = 0x00000001 << 2
playerNode.collisionBitMask = floorCategory | stoneCategory

上面提到的contact代理其实就是场景类继承的SKPhysicsContactDelegate,如果物理实体发生碰撞,需要完成一些操作的话,可以写进代理类的函数didBegin,如果contactTestBitMask为0的话,任何碰撞都无法捕获。

func didBegin(_ contact: SKPhysicsContact){
    if (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) == (playerCategory | stoneCategory) {
        // 补充碰撞后的响应
    }
}

4. 向物理实体施加力的作用

func applyForce(CGVector) // 向物理体重心施加力
func applyTorque(CGFloat) // 转矩
func applyForce(CGVector, at: CGPoint) // 向给定点施加力
func applyImpulse(CGVector) // 向物理体重心施加冲量
func applyAngularImpulse(CGFloat) // 向物理体重心施加角动量
func applyImpulse(CGVector, at: CGPoint) // 向给定点施加冲量

如果希望使玩家人物跳跃,可以对玩家节点施加垂直向上的冲量:

playerNode.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 300))

如果希望在调试时显示物理实体,可以在GameViewController中添加如下代码,运行程序时每一个物理实体会出现蓝色边缘。

self.view.showsPhysics = true

引用这篇博客的话总结:

完成游戏的物理系统设置,需要完成以下几点:

  1. 把SKPhysicsBody关联到拥有物理属性的游戏对象上,每一个游戏对象node节点都有physicsbody属性
  2. 设置physicsbody的各个属性,让游戏对象在游戏里面呈现更好的游戏效果
  3. 设置整个游戏世界的物理系统,比如重力,注意,scene也有物理属性
  4. 可以在physicsbody上施加力
  5. 定义物体之间的碰撞处理
  6. 优化整个物理系统
   
 摸鱼堡版权所有丨如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明转自:http://moyubao.net/coder/201/

发表评论

邮箱地址不会被公开。