First, let’s start by downloading the initial project. Set it up and run, and an alert window should appear asking you to allow the camera to be used in your application.

Click OK, and if all goes well, you should see the camera view.

In addition, this tutorial builds on the knowledge from previous tutorials, so feel free to consult the ARKit tutorial series if you encounter difficulties in this article.

To understand the Physics Body

We’ll start with the physics Body, which is one of the basic components for building your application. In order for SceneKit to know how to simulate a SceneKit node in your application, we’ll need to attach a SCNPhysicsBody. SCNPhysicsBody is an object that adds physics Simulation to a Node.

SceneKit performs physics calculations to nodes with additional physics bodies before rendering each frame. These calculations include gravity, friction, and collisions with other objects. You can also apply pressure and momentum to the body. After these calculations, it updates the node position and orientation before rendering the frame.

Basically, before each frame is rendered, a physical calculation is done.

As a developer, it is particularly important to have a learning atmosphere and a communication circle. This is my iOS communication group: 1012951431. No matter you are a big bull or a small white, you are welcome to enter.

Physics Body type

To build the Physics Body, you first need to specify the Physics Body Type. A Physics Body Type determines how a physics body interacts with external pressures and other objects. Three physics body types are static, dynamic and kinematic respectively.

Static

SceneKit objects of the floor, wall, and terrain types are suitable for using static physics Body Type. Static types are not affected by force or collision and cannot be moved.

Dynamic

If you want to create effects like flying fire-breathing dragons, Steph Curry baskets, or rocket blasts, you should want to use a dynamic physics Body type SceneKit object. Dynamic types are physics bodies that can be affected by stress and collisions.

Kinematic

When would you want to use an kinematic physics body? For example, when you want to create a game that requires pushing blocks with your finger, you create an “invisible” push block that is triggered by finger movement. This “invisible” push block is not affected by other blocks, but it can push other blocks on contact. A motion type is a physics body that is not affected by momentum or collisions, but can impact other objects as it moves. In other words, it can actively affect others, but cannot be affected.

Create a Physics Body

Let’s start with a static physics body for Detected Horizontal Plane so that we have a solid foundation to keep our ship on its feet.

Add the following method to viewController.swift’s renderer(_:didUpdate:for:) :

func update(_ node: inout SCNNode, withGeometry geometry: SCNGeometry, type: SCNPhysicsBodyType) {
    let shape = SCNPhysicsShape(geometry: geometry, options: nil)
    let physicsBody = SCNPhysicsBody(type: type, shape: shape)
    node.physicsBody = physicsBody
}
Copy the code

In this method, we create a SCNPhysicsShape object. A SCNPhysicsShape object represents the shape of the physics Body. When SceneKit detects the SCNPhysicsBody object connection for your scene, it will use the physics Shapes you define. Not something visible, rendered geometry.

Next, we pass.static to the type parameter and to the SCNPhysicsShape object to create a SCNPhysicsBody object.

Then, we set the physics Body of the node to the physics Body we just created.

Attach a static Physics Body

Now we’ll append a static physics body to the plane detected inside the renderer(_:didAdd:for:) method. Before adding the planeNode as a child node, call the following methods:

update(&planeNode, withGeometry: plane, type: .static)
Copy the code

After the change, your renderer(_:didAdd:for:) method should now look like this:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else { return } let width = CGFloat(planeAnchor.extent.x) let height = CGFloat(planeAnchor.extent.z) let plane = SCNPlane(width: width, height: height) plane.materials.first? .diffuse.contents = UIColor.transparentWhite var planeNode = SCNNode(geometry: plane) let x = CGFloat(planeAnchor.center.x) let y = CGFloat(planeAnchor.center.y) let z = CGFloat(planeAnchor.center.z)  planeNode.position = SCNVector3(x,y,z) planeNode.eulerAngles.x = -.pi / 2 update(&planeNode, withGeometry: plane, type: .static) node.addChildNode(planeNode) }Copy the code

When our detection platform receives new information and updates, it may change geometry. Therefore, we need to call the same method in render(_:didUpdate:for:) :

update(&planeNode, withGeometry: plane, type: .static)
Copy the code

Now the modified render(_:didUpdate:for:) method should look something like this:

    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as?  ARPlaneAnchor,
            var planeNode = node.childNodes.first,
            let plane = planeNode.geometry as? SCNPlane
            else { return }
        
        let width = CGFloat(planeAnchor.extent.x)
        let height = CGFloat(planeAnchor.extent.z)
        plane.width = width
        plane.height = height
        
        let x = CGFloat(planeAnchor.center.x)
        let y = CGFloat(planeAnchor.center.y)
        let z = CGFloat(planeAnchor.center.z)
        
        planeNode.position = SCNVector3(x, y, z)
        
        update(&planeNode, withGeometry: plane, type: .static)
    }
Copy the code

Good job!

Attach the Dynamic Physics Body

To make the rocket node subject to pressure and impact, now we’re going to give the node a Dynamic Physics body. Declare a rocketship node name constant in ViewController:

let rocketshipNodeName = "rocketship"
Copy the code

Then in the addRocketshipToSceneView(withGestureRecognizer:) method, add the following code after the rocketship node position adjustment code:

let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
rocketshipNode.physicsBody = physicsBody
rocketshipNode.name = rocketshipNodeName
Copy the code

We gave rocketshipNode a name and a Dynamic Physics body, which we’ll use to identify rocketshipNode in a moment. Set it up and run it, and you should see the following results:

force

We need to apply power to the ship now.

To do that, we need a method that triggers the action, and UISwipeGestureRecognizer can help us do that. First, add the following code under the addRocketshipToSceneView(withGestureRecognizer:) method:

func getRocketshipNode(from swipeLocation: CGPoint) -> SCNNode? { let hitTestResults = sceneView.hitTest(swipeLocation) guard let parentNode = hitTestResults.first? .node.parent, parentNode.name == rocketshipNodeName else { return nil } return parentNode }Copy the code

This method helps us get the rocket ship node from the position of the sliding gesture. You may be wondering why we need to set multiple conditions to unpack parentNode, because the return node from hitTestResults could be any of the five nodes that make up the rocket.

Just below the previous method, add the following function:

@objc func applyForceToRocketship(withGestureRecognizer recognizer: UIGestureRecognizer) {
    // 1
    guard recognizer.state == .ended else { return }
    // 2
    let swipeLocation = recognizer.location(in: sceneView)
    // 3
    guard let rocketshipNode = getRocketshipNode(from: swipeLocation),
        let physicsBody = rocketshipNode.physicsBody
        else { return }
    // 4
    let direction = SCNVector3(0, 3, 0)
    physicsBody.applyForce(direction, asImpulse: true)
}
Copy the code

From the code above, we do the following:

  1. Confirm that the swiping status is finished.
  2. Get hit test results from the sliding position.
  3. See if the swipe gesture was performed on the rocket ship.
  4. We apply a y-direction force to the physics body of the parent node. If you’ve noticed, we also set the impact parameter to true, which applies to the instantaneous change in the impact to speed up the physics body immediately. Basically, this option, when set to true, simulates the instant effect of an object being fired.

Great, let’s get it up and running. Please slide the ship up, you should be able to exert a force on your ship!

Add SceneKit Particle System and change the physics properties

Start with a reactor SceneKit particle System in the “Particles” folder.

In this tutorial, we will not cover how to create a SceneKit particle system. Let’s continue to learn how to add the SceneKit particle system to the node and its physics properties.

Open viewController.swift and declare the following variables in ViewController:

var planeNodes = [SCNNode]()
Copy the code

Inside the renderer(_:didAdd:for:) function, add the following line of code as the last line inside the function:

planeNodes.append(planeNode)
Copy the code

Simply put, when a new plane is detected, it is attached to the planeNodes array. We will assign planeNodes to the colliderNodes property of reactorParticleSystem later.

Below the renderer(_:didAdd:for:) function, implement the delegate method below:

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { guard anchor is ARPlaneAnchor, let planeNode = node.childNodes.first else { return } planeNodes = planeNodes.filter { $0 ! = planeNode } }Copy the code

This delegate method is called when the SceneKit node corresponding to a removed ARAnchor is removed from the scene. At the same time, we filter the plane nodes that have been removed from the planeNodes array.

Next, add the following method directly below the applyForceToRocketship(withGestureRecognizer:) function:

@objc func launchRocketship(withGestureRecognizer recognizer: UIGestureRecognizer) { // 1 guard recognizer.state == .ended else { return } // 2 let swipeLocation = recognizer.location(in: sceneView) guard let rocketshipNode = getRocketshipNode(from: swipeLocation), let physicsBody = rocketshipNode.physicsBody, let reactorParticleSystem = SCNParticleSystem(named: "reactor", inDirectory: nil), let engineNode = rocketshipNode.childNode(withName: "node2", recursively: false) else { return } // 3 physicsBody.isAffectedByGravity = false physicsBody.damping = 0 // 4 reactorParticleSystem.colliderNodes = planeNodes // 5 engineNode.addParticleSystem(reactorParticleSystem) // 6 let Action = scnAction. moveBy(x: 0, y: 0.3, z: 0, duration: 3) action.timingMode = .easeInEaseOut rocketshipNode.runAction(action) }Copy the code

In the above code, we do the following:

  1. Make sure the swiping gesture state is over.
  2. Unwrapped rocketshipNode and physicsBody safely as before. In addition, reactorParticleSystem and engineNode are securely unpacked. Want to add reactorParticleSystem to the ship’s engine
  3. The physics bodyAffected by gravitySet the property to false. Gravity will no longer affect the spacecraft node. We also set the damping property to zero, which simulates the impact of fluid friction or air resistance on the body. If set to zero, the influence of fluid friction or air resistance on the physics body of the rocket node can be zero.
  4. Set planeNodes to colliderNodes of the reactorParticleSystem. This would allow the particles in the particle system to bounce off the detection plane on contact, rather than fly straight through them.
  5. Add reactorParticleSystem to engineNode.
  6. We moved the rocket node up 0.3 meters and selected the easeInEaseOut animation effect.

Add a swipe gesture

Swipe Gesture recognizers need to be added to our scene view before applying pressure to launch the rocket. Please add this code under addTapGestureToSceneView() :

func addSwipeGesturesToSceneView() {
    let swipeUpGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.applyForceToRocketship(withGestureRecognizer:)))
    swipeUpGestureRecognizer.direction = .up
    sceneView.addGestureRecognizer(swipeUpGestureRecognizer)
    
    let swipeDownGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(ViewController.launchRocketship(withGestureRecognizer:)))
    swipeDownGestureRecognizer.direction = .down
    sceneView.addGestureRecognizer(swipeDownGestureRecognizer)
}
Copy the code

The gesture swiping up applies force to the ship node, swiping down launches the rocket, Nice!

Finally, call it in viewDidLoad() :

addSwipeGesturesToSceneView()
Copy the code

That’s all for this article!

Showtime!

Congratulations, now it’s time to show off your achievements. Swipe down and see what you get!

Slide down and the rocket takes off!

conclusion

I hope you enjoyed this tutorial and learned something valuable. We also welcome readers to share this tutorial on social networks so your friends can get the knowledge too!

For your reference, you can download sample projects on GitHub.

Recommended at the end of the article: iOS popular anthology

  • 1.BAT and other major manufacturers iOS interview questions + answers

  • 2.Must-read books for Advanced Development in iOS (Classics must Read)

  • 3.IOS Development Advanced Interview “resume creation” guide

  • (4)IOS Interview Process to the basics