18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

蘋果API搬運工發表於2019-04-10

ARKit系列文章目錄

本文是Ray Wenderlich上《ARKit by Tutorials》的讀書筆記,主要講內容概要和讀後感  

本文中,我們將通過一個Monster Truck小遊戲的例子,學習SceneKit中的一些特殊的物理效果.你一定不知道:SceneKit中內建了正宗的車輛物理效果!

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

基本結構

共三部分:車身(Body),車軸(Axle),車輪(Wheel)

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果
18-《ARKit by Tutorials》讀書筆記5:特殊物理效果
18-《ARKit by Tutorials》讀書筆記5:特殊物理效果
18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

在SceneKit的編輯器中進行組裝:

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果
18-《ARKit by Tutorials》讀書筆記5:特殊物理效果
18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

注意,車輪是通過一個軸來連線到真正的車軸(Axle)上的.

在程式碼中載入這些部件:

// 1 
let truckScene = SCNScene(
  named: "MonsterTruck.scnassets/Models/MonsterTruck.scn")!
truckNode = truckScene.rootNode.childNode(
  withName: "Truck", recursively: true)
wheelFLNode = truckScene.rootNode.childNode(
  withName: "Wheel_FL", recursively: true)
wheelFRNode = truckScene.rootNode.childNode(
  withName: "Wheel_FR", recursively: true)
wheelRLNode = truckScene.rootNode.childNode(
  withName: "Wheel_RL", recursively: true)
wheelRRNode = truckScene.rootNode.childNode(
  withName: "Wheel_RR", recursively: true)
// 2  
truckNode.addChildNode(wheelFLNode!)
truckNode.addChildNode(wheelFRNode!)
truckNode.addChildNode(wheelRLNode!)
truckNode.addChildNode(wheelRRNode!)
// 3
truckNode.isHidden = true
sceneView.scene.rootNode.addChildNode(truckNode)
複製程式碼

新增車輛物理效果

SceneKit中有專用的物理效果:

  • SCNPhysicsVehicle:讓一個標準的物理形體表現的像一輛車.
  • SCNPhysicsBody:正常情況下的標準物理形體.用在車身類物體上.這類形體在建立過程中將會是SCNPhysicsVehicle型別的.
  • SCNPhysicsVehicleWheel:專用的物理形體,不僅模擬車輪的行為,還有外觀及其他物理特性.這類形體在建立過程中將會是SCNPhysicsVehicle型別的.

選中Truch節點,做如下圖設定:

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

建立車輪物理效果

定義一些常量,交賦值給SCNPhysicsVehicleWheel節點.

let wheelRadius: CGFloat = 0.04
let wheelFrictionSlip: CGFloat = 0.9
let suspensionMaxTravel: CGFloat = 4.0
let suspensionMaxForce: CGFloat = 100
let suspensionRestLength: CGFloat = 0.08
let suspensionDamping: CGFloat = 2.0
let suspensionStiffness: CGFloat = 2.0
let suspensionCompression: CGFloat = 4.0

func createPhysicsVehicleWheel(wheelNode: SCNNode,
  position: SCNVector3) -> SCNPhysicsVehicleWheel {
  let wheel = SCNPhysicsVehicleWheel(node: wheelNode)
  wheel.connectionPosition = position
  wheel.axle = SCNVector3(x: -1.0, y: 0, z: 0)
  wheel.maximumSuspensionTravel = suspensionMaxTravel
  wheel.maximumSuspensionForce = suspensionMaxForce
  wheel.suspensionRestLength = suspensionRestLength
  wheel.suspensionDamping = suspensionDamping
  wheel.suspensionStiffness = suspensionStiffness
  wheel.suspensionCompression = suspensionCompression
  wheel.radius = wheelRadius
  wheel.frictionSlip = wheelFrictionSlip
  return wheel
}
複製程式碼

各個常量的含義:

  • Wheel Radius(車輪半徑):車輪物理外觀的實際半徑.
  • Wheel Friction Slip(車輪摩擦滑動): 指定車輪自身和接觸表面的摩擦力(個人理解就是最大靜摩擦力,超過後會打滑).
  • Suspension Maximum Travel(懸架最大行程): 定義了車輪允許沿連線點上下移動的最大行程.單位是釐米.
  • Suspension Maximum Force(懸架最大受力): 定義懸架受到的車輪和車身的最大受力.單位是牛頓.
  • Suspension Rest Length(懸架靜息長度): 定義懸架在靜止不動時的長度.單位是米.
  • Suspension Damping(懸架阻尼): 定義了懸架在振盪中的阻尼係數.
  • Suspension Stiffness(懸架剛度). 定義了車輪自身和車輛底盤之間的彈簧係數.
  • Suspension Compression(懸架壓縮回彈): 定義了懸架在受到壓縮後,回到靜息狀態的速度.

全車的物理效果

將車輪繫結在車身上,並使用物理效果:

func createVehiclePhysics() {
  // 1
  if physicsVehicle != nil {
    sceneView.scene.physicsWorld.removeBehavior(physicsVehicle)
  }
  //2 
  let wheelFL = createPhysicsVehicleWheel(
    wheelNode: wheelFLNode!,
    position: SCNVector3(x: -0.07, y: 0.04, z: 0.06))
  let wheelFR = createPhysicsVehicleWheel(
    wheelNode: wheelFRNode!,
    position: SCNVector3(x: 0.07, y: 0.04, z: 0.06))
  let wheelRL = createPhysicsVehicleWheel(
    wheelNode: wheelRLNode!,
    position: SCNVector3(x: -0.07, y: 0.04, z: -0.06))
  let wheelRR = createPhysicsVehicleWheel(
    wheelNode: wheelRRNode!,
    position: SCNVector3(x: 0.07, y: 0.04, z: -0.06))
  // 3     
  physicsVehicle = SCNPhysicsVehicle(
    chassisBody: truckNode.physicsBody!,
    wheels: [wheelFL, wheelFR, wheelRL, wheelRR])
  // 4
  sceneView.scene.physicsWorld.addBehavior(physicsVehicle)
}
複製程式碼

初始化時放置的位置,將車輛放在聚焦框focusNode的上面:

func updatePositions() {
  // 1
  self.truckNode.position = self.focusNode.position
  self.truckNode.position.y += 0.20
  
  // 2
  self.truckNode.physicsBody?.velocity = SCNVector3Zero
  self.truckNode.physicsBody?.angularVelocity = SCNVector4Zero
  
  // 3
  self.truckNode.physicsBody?.resetTransform()
}
複製程式碼

此外,還有新增地面,設定遊戲狀態等.

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

新增引擎的力

我們想讓使用者點選螢幕時,加速前進,鬆開後緩慢減速

var maximumSpeed: CGFloat = 2.0

var isThrottling = false
var engineForce: CGFloat = 0
let defaultEngineForce: CGFloat = 10.0

var brakingForce: CGFloat = 0
let defaultBrakingForce: CGFloat = 0.01

override func touchesBegan(_ touches: Set<UITouch>,
  with event: UIEvent?) {
  isThrottling = true
}
    
override func touchesEnded(_ touches: Set<UITouch>,
  with event: UIEvent?) {
  isThrottling = false
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    DispatchQueue.main.async {
     self.updateStatus()
     self.updateFocusNode()
     self.updateVehiclePhysics()
   }
}
  
func updateVehiclePhysics() {
  // 1
  guard self.gameState == .playGame else { return }
  // 2
  if isThrottling {
   engineForce = defaultEngineForce
   brakingForce = 0
 } else {
   engineForce = 0
   brakingForce = defaultBrakingForce
 }
  // 3 apply方法是SCNPhysicsVehicle自帶的方法
  physicsVehicle.applyEngineForce(engineForce, forWheelAt: 0)
  physicsVehicle.applyEngineForce(engineForce, forWheelAt: 1)
  physicsVehicle.applyEngineForce(engineForce, forWheelAt: 2)
  physicsVehicle.applyEngineForce(engineForce, forWheelAt: 3)    

  physicsVehicle.applyBrakingForce(brakingForce, forWheelAt: 0)
  physicsVehicle.applyBrakingForce(brakingForce, forWheelAt: 1)
  physicsVehicle.applyBrakingForce(brakingForce, forWheelAt: 2)
  physicsVehicle.applyBrakingForce(brakingForce, forWheelAt: 3)

    // Limit Speed
    if self.physicsVehicle.speedInKilometersPerHour >
      CGFloat(maximumSpeed) {
      engineForce = CGFloat(0.0)
    }
}
複製程式碼

控制方向

可以用CoreMotion框架來控制車輛方向.

let motionManager = CMMotionManager()
let steeringClamp: CGFloat = 0.6
var steeringAngle: CGFloat = 0


func updateSteeringAngle(acceleration: CMAcceleration) {
  steeringAngle = (CGFloat)(acceleration.y)
  
  if steeringAngle < -steeringClamp {
    steeringAngle = -steeringClamp;
  } else if steeringAngle > steeringClamp {
    steeringAngle = steeringClamp;
  }
}

func startAccelerometer() {
  // 1
  guard motionManager.isAccelerometerAvailable else { return }
  // 2
  motionManager.accelerometerUpdateInterval = 1/60.0
  // 3
  motionManager.startAccelerometerUpdates(
    to: OperationQueue.main,
    withHandler: { (accelerometerData: CMAccelerometerData?,
      error: Error?) in
      self.updateSteeringAngle(acceleration:
        accelerometerData!.acceleration)
  })
}


func stopAccelerometer() {
  motionManager.stopAccelerometerUpdates()
}
複製程式碼

此外,還要在updateVehiclePhysics()方法中新增,才能使用,這也是系統自帶的處理車輛轉向的方法:

physicsVehicle.setSteeringAngle(steeringAngle, forWheelAt: 0)
physicsVehicle.setSteeringAngle(steeringAngle, forWheelAt: 1)
複製程式碼

18-《ARKit by Tutorials》讀書筆記5:特殊物理效果

第五部分讀書筆記結束!請期待第二版的讀書筆記。

ARKit是在WWDC2017上推出的,2018春季更新了ARKit 1.5版本.《ARKit by Tutorials》第一版寫作完成時,WWDC2018還未召開,因此第一版書中內容較為簡單,也沒有涉及到ARKit 2.0的新特性:世界地圖,圖片追蹤,3D物體檢測等.
後續第二版更新已在2018年秋季釋出,新增了兩章ARKit 2.0的Demo及講解,我會持續更新該讀書筆記系列.

相關文章