Advertisement
  1. Code
  2. SceneKit

Eine Einführung in SceneKit: Benutzerinteraktion, Animationen & Physik 

Scroll to top
Read Time: 11 min

German (Deutsch) translation by Katharina Grigorovich-Nevolina (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Das ist der zweite Teil unserer Einführungsserie auf SceneKit.  In diesem Tutorial gehe ich davon aus, dass Sie mit den im ersten Teil erläuterten Konzepten vertraut sind, einschließlich der Einrichtung einer Szene mit Lichtern, Schatten, Kameras, Knoten und Materialien.

In diesem Tutorial werde ich Ihnen einige der komplizierteren, aber auch nützlicheren Funktionen von SceneKit vorstellen, wie z. B. Animation, Benutzerinteraktion, Partikelsysteme und Physik.  Durch das Implementieren dieser Features können Sie interaktive und dynamische 3D-Inhalte anstelle von statischen Objekten erstellen, wie im vorherigen Lernprogramm.

1. Einrichten der Szene

Erstellen Sie ein neues Xcode-Projekt basierend auf der Vorlage iOS > Application > Einzelansicht Anwendungsvorlage (iOS > Application > Single View Application template).

iOS App TemplateiOS App TemplateiOS App Template

Benennen Sie das Projekt, legen Sie Language to Swift und Devices to Universal fest.

App InformationApp InformationApp Information

Öffnen Sie ViewController.swift und importieren Sie das SceneKit-Framework.

1
import UIKit
2
import SceneKit

Deklarieren Sie als Nächstes die folgenden Eigenschaften in der ViewController-Klasse.

1
var sceneView: SCNView!
2
var camera: SCNNode!
3
var ground: SCNNode!
4
var light: SCNNode!
5
var button: SCNNode!
6
var sphere1: SCNNode!
7
var sphere2: SCNNode!

Wir richten die Szene in der viewDidLoad-Methode wie unten gezeigt ein.

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    sceneView = SCNView(frame: self.view.frame)
5
    sceneView.scene = SCNScene()
6
    self.view.addSubview(sceneView)
7
    
8
    let groundGeometry = SCNFloor()
9
    groundGeometry.reflectivity = 0
10
    let groundMaterial = SCNMaterial()
11
    groundMaterial.diffuse.contents = UIColor.blueColor()
12
    groundGeometry.materials = [groundMaterial]
13
    ground = SCNNode(geometry: groundGeometry)
14
    
15
    let camera = SCNCamera()
16
    camera.zFar = 10000
17
    self.camera = SCNNode()
18
    self.camera.camera = camera
19
    self.camera.position = SCNVector3(x: -20, y: 15, z: 20)
20
    let constraint = SCNLookAtConstraint(target: ground)
21
    constraint.gimbalLockEnabled = true
22
    self.camera.constraints = [constraint]
23
    
24
    let ambientLight = SCNLight()
25
    ambientLight.color = UIColor.darkGrayColor()
26
    ambientLight.type = SCNLightTypeAmbient
27
    self.camera.light = ambientLight
28
    
29
    let spotLight = SCNLight()
30
    spotLight.type = SCNLightTypeSpot
31
    spotLight.castsShadow = true
32
    spotLight.spotInnerAngle = 70.0
33
    spotLight.spotOuterAngle = 90.0
34
    spotLight.zFar = 500
35
    light = SCNNode()
36
    light.light = spotLight
37
    light.position = SCNVector3(x: 0, y: 25, z: 25)
38
    light.constraints = [constraint]
39
    
40
    let sphereGeometry = SCNSphere(radius: 1.5)
41
    let sphereMaterial = SCNMaterial()
42
    sphereMaterial.diffuse.contents = UIColor.greenColor()
43
    sphereGeometry.materials = [sphereMaterial]
44
    sphere1 = SCNNode(geometry: sphereGeometry)
45
    sphere1.position = SCNVector3(x: -15, y: 1.5, z: 0)
46
    sphere2 = SCNNode(geometry: sphereGeometry)
47
    sphere2.position = SCNVector3(x: 15, y: 1.5, z: 0)
48
    
49
    let buttonGeometry = SCNBox(width: 4, height: 1, length: 4, chamferRadius: 0)
50
    let buttonMaterial = SCNMaterial()
51
    buttonMaterial.diffuse.contents = UIColor.redColor()
52
    buttonGeometry.materials = [buttonMaterial]
53
    button = SCNNode(geometry: buttonGeometry)
54
    button.position = SCNVector3(x: 0, y: 0.5, z: 15)
55
    
56
    sceneView.scene?.rootNode.addChildNode(self.camera)
57
    sceneView.scene?.rootNode.addChildNode(ground)
58
    sceneView.scene?.rootNode.addChildNode(light)
59
    sceneView.scene?.rootNode.addChildNode(button)
60
    sceneView.scene?.rootNode.addChildNode(sphere1)
61
    sceneView.scene?.rootNode.addChildNode(sphere2)
62
}

Die Implementierung von viewDidLoad sollte Ihnen bekannt vorkommen, wenn Sie den ersten Teil dieser Serie gelesen haben.  Alles, was wir tun, ist die Szene, die wir in diesem Tutorial verwenden werden.  Die einzigen neuen Dinge sind die SCNFloor-Klasse und die zFar-Eigenschaft.

Wie der Name schon sagt, wird die SCNFloor-Klasse verwendet, um einen Boden oder Boden für die Szene zu erstellen.  Dies ist viel einfacher im Vergleich zum Erstellen und Drehen eines SCNPlane wie im vorherigen Tutorial.

Die zFar-Eigenschaft bestimmt, wie weit die Kamera in der Entfernung sehen kann oder wie weit das Licht von einer bestimmten Quelle entfernt sein kann.  Erstellen und führen Sie Ihre App aus.  Ihre Szene sollte ungefähr so aussehen:

Initial sceneInitial sceneInitial scene

2. Benutzerinteraktion

Benutzerinteraktion wird in SceneKit durch eine Kombination der UIGestureRecognizer-Klassen- und Treffertests behandelt.  Um beispielsweise ein Antippen zu erkennen, fügen Sie zuerst einem SCNView einen UITapGestureRecognizer hinzu, ermitteln die Position des Tap in der Ansicht und sehen, ob er mit einem der Knoten in Kontakt steht oder auf einen dieser Knoten trifft.

Um besser zu verstehen, wie dies funktioniert, verwenden wir ein Beispiel.  Wenn ein Knoten angetippt wird, entfernen wir ihn aus der Szene.  Fügen Sie der viewDidLoad-Methode der ViewController-Klasse das folgende Code-Snippet hinzu:

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    sceneView = SCNView(frame: self.view.frame)
5
    sceneView.scene = SCNScene()
6
    self.view.addSubview(sceneView)
7
        
8
    let tapRecognizer = UITapGestureRecognizer()
9
    tapRecognizer.numberOfTapsRequired = 1
10
    tapRecognizer.numberOfTouchesRequired = 1
11
    tapRecognizer.addTarget(self, action: "sceneTapped:")
12
    sceneView.gestureRecognizers = [tapRecognizer]
13
        
14
    ...
15
}

Fügen Sie als Nächstes der ViewController-Klasse die folgende Methode hinzu:

1
func sceneTapped(recognizer: UITapGestureRecognizer) {
2
    let location = recognizer.locationInView(sceneView)
3
    
4
    let hitResults = sceneView.hitTest(location, options: nil)
5
    if hitResults?.count > 0 {
6
        let result = hitResults![0] as! SCNHitTestResult
7
        let node = result.node
8
        node.removeFromParentNode()
9
    }
10
}

Bei dieser Methode erhalten Sie zuerst die Position des Tap als CGPoint.  Als Nächstes verwenden Sie diesen Punkt, um einen Treffer-Test für das sceneView-Objekt  durchzuführen und die SCNHitTestResult-Objekte in einem Array namens hitResults zu speichern.  Der options-Parameter dieser Methode kann ein Wörterbuch mit Schlüsseln und Werten enthalten, über das Sie in der Dokumentation von Apple nachlesen können.  Wir überprüfen dann, ob der Treffertest mindestens ein Ergebnis zurückgegeben hat, und wenn dies der Fall ist, entfernen wir das erste Element im Array von seinem übergeordneten Knoten.

Wenn der Treffertest mehrere Ergebnisse zurückgibt, werden die Objekte nach ihrer z-Position sortiert, dh nach der Reihenfolge, in der sie aus der Sicht der aktuellen Kamera angezeigt werden.  Wenn Sie beispielsweise in der aktuellen Szene auf eine der beiden Kugeln oder auf die Schaltfläche tippen, wird der angetippte Knoten das erste Element im zurückgegebenen Array bilden.  Da der Boden jedoch aus Sicht der Kamera direkt hinter diesen Objekten erscheint, ist der Bodenknoten ein weiterer Punkt in der Ergebnismatrix, der zweite in diesem Fall.  Das geschieht, weil ein Abgriff an derselben Stelle den Bodenknoten treffen würde, wenn die Kugeln und der Knopf nicht vorhanden wären.

Erstellen und starten Sie Ihre App und tippen Sie auf die Objekte in der Szene.  Sie sollten verschwinden, wenn Sie auf jeden tippen.

Scene with some deleted nodesScene with some deleted nodesScene with some deleted nodes

Jetzt, da wir feststellen können, wann ein Knoten angetippt wurde, können wir mit dem Hinzufügen von Animationen beginnen.

3. Animation

Es gibt zwei Klassen, die zum Ausführen von Animationen in SceneKit verwendet werden können:

  • SCNAction
  • SCNTransaktion

SCNAction-Objekte sind sehr nützlich für einfache und wiederverwendbare Animationen wie Bewegung, Rotation und Skalierung.  Sie können beliebig viele Aktionen zu einem benutzerdefinierten Aktionsobjekt kombinieren.

Die SCNTransaction-Klasse kann dieselben Animationen ausführen, ist jedoch in mancher Hinsicht vielseitiger, z. B. beim Animieren von Materialien.  Diese zusätzliche Vielseitigkeit geht jedoch auf Kosten von SCNTransaction-Animationen, die nur die gleiche Wiederverwendbarkeit wie eine Funktion haben und der Aufbau über Klassenmethoden erfolgt.

Für Ihre erste Animation zeige ich Ihnen Code mit den Klassen SCNAction und SCNTransaction.  In diesem Beispiel wird der Button nach unten bewegt und weiß, wenn er angetippt wird.  Aktualisieren Sie die Implementierung der Methode sceneTapped(_:) wie unten gezeigt.

1
func sceneTapped(recognizer: UITapGestureRecognizer) {
2
    let location = recognizer.locationInView(sceneView)
3
        
4
    let hitResults = sceneView.hitTest(location, options: nil)
5
    if hitResults?.count > 0 {
6
        let result = hitResults![0] as! SCNHitTestResult
7
        let node = result.node
8
            
9
        if node == button {
10
            SCNTransaction.begin()
11
            SCNTransaction.setAnimationDuration(0.5)
12
            let materials = node.geometry?.materials as! [SCNMaterial]
13
            let material = materials[0]
14
            material.diffuse.contents = UIColor.whiteColor()
15
            SCNTransaction.commit()
16
                
17
            let action = SCNAction.moveByX(0, y: -0.8, z: 0, duration: 0.5)
18
            node.runAction(action)
19
        }
20
    }
21
}

In der Methode sceneTapped(_:) erhalten wir einen Verweis auf den Knoten, den der Benutzer angetippt hat, und prüfen, ob das die Taste in der Szene ist.  Wenn das der Fall ist, animieren wir sein Material von rot nach weiß mit der SCNTransaction-Klasse und verschieben es entlang der y-Achse in einer negativen Richtung mithilfe einer SCNAction-Instanz.  Die Dauer der Animation ist auf 0,5 Sekunden eingestellt.

Erstellen und starten Sie Ihre App erneut und tippen Sie auf die Schaltfläche.  Es sollte sich nach unten bewegen und seine Farbe in Weiß ändern, wie im folgenden Screenshot gezeigt.

Animated buttonAnimated buttonAnimated button

4. Physik

Mit dem SceneKit-Framework können Sie realistische physikalische Simulationen erstellen.  Die Funktionalität, die die Physiksimulationen von SceneKit bieten, ist umfangreich und reicht von einfachen Geschwindigkeiten, Beschleunigungen und Kräften über Gravitations- und elektrische Felder bis hin zur Kollisionserkennung.

Was Sie in der aktuellen Szene tun werden, ist, ein Gravitationsfeld auf eine der Kugeln aufzubringen, so dass die zweite Kugel aufgrund der Schwerkraft in Richtung der ersten Kugel gezogen wird.  Diese Schwerkraft wird aktiv, wenn die Taste gedrückt wird.

Das Setup für diese Simulation ist sehr einfach.  Verwenden Sie ein SCNPhysicsBody-Objekt für jeden Knoten, für den Sie von der Physiksimulation betroffen sein sollen, und ein SCNPhysicsField-Objekt für jeden Knoten, der die Quelle eines Felds sein soll.  Aktualisieren Sie die viewDidLoad-Methode wie unten gezeigt.

1
override func viewDidLoad() {
2
    ...
3
4
    buttonGeometry.materials = [buttonMaterial]
5
    button = SCNNode(geometry: buttonGeometry)
6
    button.position = SCNVector3(x: 0, y: 0.5, z: 15)
7
    
8
    // Physics

9
    let groundShape = SCNPhysicsShape(geometry: groundGeometry, options: nil)
10
    let groundBody = SCNPhysicsBody(type: .Kinematic, shape: groundShape)
11
    ground.physicsBody = groundBody
12
    
13
    let gravityField = SCNPhysicsField.radialGravityField()
14
    gravityField.strength = 0
15
    sphere1.physicsField = gravityField
16
    
17
    let shape = SCNPhysicsShape(geometry: sphereGeometry, options: nil)
18
    let sphere1Body = SCNPhysicsBody(type: .Kinematic, shape: shape)
19
    sphere1.physicsBody = sphere1Body
20
    let sphere2Body = SCNPhysicsBody(type: .Dynamic, shape: shape)
21
    sphere2.physicsBody = sphere2Body
22
    
23
    sceneView.scene?.rootNode.addChildNode(self.camera)
24
    sceneView.scene?.rootNode.addChildNode(ground)
25
    sceneView.scene?.rootNode.addChildNode(light)
26
    
27
    ...
28
}

Wir beginnen mit der Erstellung einer SCNPhysicsShape-Instanz, die die tatsächliche Form des Objekts angibt, das an der Physiksimulation beteiligt ist.  Für die grundlegenden Formen, die Sie in dieser Szene verwenden, sind die Geometrieobjekte vollkommen in Ordnung.  Bei komplizierten 3D-Modellen ist es jedoch besser, mehrere primitive Formen zu kombinieren, um eine ungefähre Form Ihres Objekts für die Physiksimulation zu erstellen.

Aus dieser Form erstellen Sie dann eine SCNPhysicsBody-Instanz und fügen sie dem Boden der Szene hinzu.  Das ist notwendig, da jede SceneKit-Szene standardmäßig ein vorhandenes Gravitationsfeld besitzt, das jedes Objekt nach unten zieht.  Der Kinematic-Typ, den Sie diesem SCNPhysicsBody geben, bedeutet, dass das Objekt an Kollisionen beteiligt ist, aber nicht von Kräften beeinflusst wird (und nicht aufgrund der Schwerkraft fallen wird).

Als Nächstes erstellen Sie das Gravitationsfeld und weisen es dem ersten Sphärenknoten zu.  Nach dem gleichen Prozess wie für den Boden erstellen Sie dann einen Physikkörper für jede der beiden Sphären.  Sie geben die zweite Sphäre jedoch als Dynamic-Physikkörper an, weil Sie möchten, dass sie von dem von Ihnen erzeugten Gravitationsfeld beeinflusst und bewegt wird.

Zuletzt müssen Sie die Stärke dieses Feldes einstellen, um es zu aktivieren, wenn die Schaltfläche angetippt wird.  Fügen Sie der Methode sceneTapped(_:) die folgende Zeile hinzu:

1
func sceneTapped(recognizer: UITapGestureRecognizer) {
2
    ...
3
        if node == button {
4
            ...
5
            sphere1.physicsField?.strength = 750
6
        }
7
    }
8
}

Erstellen und starten Sie Ihre App, tippen Sie auf die Schaltfläche und beobachten Sie, wie sich die zweite Kugel langsam auf die erste beschleunigt.  Beachten Sie, dass es einige Sekunden dauern kann, bis sich die zweite Kugel bewegt.

First sphere moves towards the secondFirst sphere moves towards the secondFirst sphere moves towards the second

Es gibt nur noch eine Sache, die Kugeln explodieren zu lassen, wenn sie kollidieren.

5. Kollisionserkennung und Partikelsysteme

Um den Effekt einer Explosion zu erzeugen, nutzen wir die SCNParticleSystem-Klasse.  Ein Partikelsystem kann von einem externen 3D-Programm, Quellcode oder, wie ich Ihnen gleich zeigen werde, Xcode's Partikelsystemeditor erstellt werden.  Erstellen Sie eine neue Datei, indem Sie Command+N drücken, und wählen Sie Scenekit Particle System im Abschnitt iOS > Ressource (iOS > Resource section).

Particle system templateParticle system templateParticle system template

Partikelsystem VorlageStellen Sie die Partikelsystemvorlage auf Reactor ein.  Klicken Sie auf Weiter, benennen Sie die Datei Explosion und speichern Sie sie in Ihrem Projektordner.

Particle system typeParticle system typeParticle system type

Im Projektnavigator sehen Sie nun zwei neue Dateien, Explosion.scnp und spark.png.  Das Bild spark.png ist eine vom Partikelsystem verwendete Ressource, die Ihrem Projekt automatisch hinzugefügt wird.  Wenn Sie Explosion.scnp öffnen, werden Sie in Xcode in Echtzeit animiert und gerendert angezeigt.  Der Partikelsystemeditor ist ein sehr leistungsfähiges Werkzeug in Xcode und ermöglicht es Ihnen, ein Partikelsystem anzupassen, ohne es programmatisch zu machen.

Xcodes particle system editorXcodes particle system editorXcodes particle system editor

Wenn das Partikelsystem geöffnet ist, gehen Sie rechts zum Attribute-Inspektor und ändern Sie die folgenden Attribute im Emitter-Bereich:

  • Geburtenrate bis 300
  • Richtungsmodus auf Zufällig

Ändern Sie die folgenden Attribute im Simulation-Abschnitt :

  • Lebensdauer bis 3
  • Geschwindigkeitsfaktor auf 2

Und schließlich, ändern Sie die folgenden Attribute im Abschnitt Lebenszyklus:

  • Emission dur. bis 1
  • Looping to Plays einmal
Particle system attributes 1Particle system attributes 1Particle system attributes 1
Particle system attributes 2Particle system attributes 2Particle system attributes 2

Ihr Partikelsystem sollte nun in alle Richtungen schießen und ähnlich dem folgenden Screenshot aussehen:

Finished particle systemFinished particle systemFinished particle system

Öffnen Sie ViewController.swift und sorgen Sie dafür, dass Ihre ViewController-Klasse dem SCNPhysicsContactDelegate-Protokoll entspricht.  Dieses Protokoll ist notwendig, um eine Kollision zwischen zwei Knoten zu erkennen.

1
class ViewController: UIViewController, SCNPhysicsContactDelegate

Ordnen Sie als Nächstes die aktuelle ViewController-Instanz als contactDelegate Ihres physicsWorld-Objekts in der viewDidLoad-Methode zu

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    sceneView = SCNView(frame: self.view.frame)
5
    sceneView.scene = SCNScene()
6
    sceneView.scene?.physicsWorld.contactDelegate = self
7
    self.view.addSubview(sceneView)
8
    
9
    ...
10
}

.Implementieren Sie schließlich die physicsWorld (_:didUpdateContact:)-Methode in der ViewController-Klasse:

1
func physicsWorld(world: SCNPhysicsWorld, didUpdateContact contact: SCNPhysicsContact) {
2
    if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) {
3
        let particleSystem = SCNParticleSystem(named: "Explosion", inDirectory: nil)
4
        let systemNode = SCNNode()
5
        systemNode.addParticleSystem(particleSystem)
6
        systemNode.position = contact.nodeA.position
7
        sceneView.scene?.rootNode.addChildNode(systemNode)
8
        
9
        contact.nodeA.removeFromParentNode()
10
        contact.nodeB.removeFromParentNode()
11
    }
12
}

Wir prüfen zunächst, ob die beiden an der Kollision beteiligten Knoten die beiden Kugeln sind.  Wenn das der Fall ist, laden wir das Partikelsystem aus der Datei, die wir gerade erstellt haben, und fügen es einem neuen Knoten hinzu.  Schließlich entfernen wir beide Sphären aus der Szene.

Erstellen und starten Sie Ihre App erneut und tippen Sie auf die Schaltfläche.  Wenn sich die Kugeln berühren, sollten sie beide verschwinden und Ihr Partikelsystem sollte erscheinen und beleben.

Explosion when the two spheres collideExplosion when the two spheres collideExplosion when the two spheres collide

Schlussfolgerung

In diesem Tutorial habe ich Ihnen gezeigt, wie Sie Benutzerinteraktion, Animation, Physiksimulation und Partikelsysteme mithilfe des SceneKit-Frameworks implementieren.  Die Techniken, die Sie in dieser Serie gelernt haben, können auf jedes Projekt mit beliebig vielen Animationen, Physiksimulationen usw. angewendet werden.

Sie sollten nun komfortabel eine einfache Szene erstellen und dynamische Elemente wie Animation und Partikel-Systeme hinzufügen.  Die Konzepte, die Sie in dieser Serie gelernt haben, sind auf die kleinste Szene mit einem einzelnen Objekt bis hin zu einem großen Spiel anwendbar.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.