Un'introduzione a SceneKit interazione con l'utente, animazioni e fisica

Cosa starai creando

Questa è la seconda parte della nostra serie introduttiva su SceneKit. In questo tutorial, presumo che tu abbia familiarità con i concetti spiegati nella prima parte, inclusa l'impostazione di una scena con luci, ombre, fotocamere, nodi e materiali.

In questo tutorial, ti spiegherò alcune delle più complicate, ma anche più utili, caratteristiche di SceneKit, come l'animazione, l'interazione dell'utente, i sistemi di particelle e la fisica. Implementando queste funzionalità, puoi creare contenuti 3D interattivi e dinamici piuttosto che oggetti statici come hai fatto nel tutorial precedente.

1. Impostazione della scena

Creare un nuovo progetto Xcode basato sul iOS> Applicazione> Applicazione vista singola modello.

Assegna un nome al progetto, imposta linguaggioveloce, e dispositiviuniversale.

Aperto ViewController.swift e importa il framework di SceneKit.

importare UIKit import SceneKit

Quindi, dichiarare le seguenti proprietà in ViewController classe.

var sceneView: SCNView! var camera: SCNNode! var ground: SCNNode! var light: SCNNode! pulsante var: SCNNode! var sphere1: SCNNode! var sphere2: SCNNode!

Abbiamo creato la scena nel viewDidLoad metodo come mostrato di seguito.

override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) let groundGeometry = SCNFloor () groundGeometry.reflectivity = 0 let groundMaterial = SCNMaterial () groundMaterial.diffuse.contents = UIColor.blueColor () groundGeometry.materials = [groundMaterial] ground = SCNNode (geometry: groundGeometry) lascia camera = SCNCamera () camera.zFar = 10000 self.camera = SCNNode () self .camera.camera = camera self.camera.position = SCNVector3 (x: -20, y: 15, z: 20) let constraint = SCNLookAtConstraint (target: ground) constraint.gimbalLockEnabled = true self.camera.constraints = [vincolo] let ambientLight = SCNLight () ambientLight.color = UIColor.darkGrayColor () ambientLight.type = SCNLightTypeAmbient self.camera.light = ambientLight lascia spotLight = SCNLight () spotLight.type = SCNLightTypeSpot spotLight.castsShadow = true spotLight.spotInnerAngle = 70.0 spotLight. spotOuterAngle = 90.0 spotLight.zFar = 500 l ight = SCNNode () light.light = spotLight light.position = SCNVector3 (x: 0, y: 25, z: 25) light.constraints = [vincolo] lascia sphereGeometry = SCNSphere (raggio: 1.5) lascia sphereMaterial = SCNMaterial () sphereMaterial.diffuse.contents = UIColor.greenColor () sphereGeometry.materials = [sphereMaterial] sphere1 = SCNNode (geometry: sphereGeometry) sphere1.position = SCNVector3 (x: -15, y: 1.5, z: 0) sphere2 = SCNNode (geometry : sphereGeometry) sphere2.position = SCNVector3 (x: 15, y: 1.5, z: 0) let buttonGeometry = SCNBox (larghezza: 4, altezza: 1, lunghezza: 4, chamferRadius: 0) let buttonMaterial = SCNMaterial () buttonMaterial. diffuse.contents = UIColor.redColor () buttonGeometry.materials = [buttonMaterial] button = SCNNode (geometry: buttonGeometry) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) sceneView.scene? .rootNode.addChildNode (self.camera) sceneView.scene? .rootNode.addChildNode (ground) sceneView.scene? .rootNode.addChildNode (light) sceneView.scene? .rootNode.addChildNode (button) sceneView.scen e? .rootNode.addChildNode (sphere1) sceneView.scene? .rootNode.addChildNode (sphere2)

L'implementazione di viewDidLoad dovrebbe sembrare familiare se hai letto la prima parte di questa serie. Tutto ciò che facciamo è impostare la scena che useremo in questo tutorial. Le uniche cose nuove includono il SCNFloor classe e il zFar proprietà.

Come suggerisce il nome, il SCNFloor la classe è usata per creare un pavimento o un terreno per la scena. Questo è molto più facile rispetto alla creazione e alla rotazione di un SCNPlane come abbiamo fatto nel precedente tutorial.

Il zFar proprietà determina quanto lontano può vedere una telecamera o quanto può raggiungere la luce proveniente da una particolare fonte. Crea ed esegui la tua app. La tua scena dovrebbe assomigliare a questa:

2. Interazione dell'utente

L'interazione dell'utente viene gestita in SceneKit da una combinazione di UIGestureRecognizer test di classe e hit. Ad esempio, per rilevare un tocco, aggiungi prima un UITapGestureRecognizer a a SCNView, determinare la posizione del rubinetto nella vista e vedere se è in contatto con o colpisce uno dei nodi.

Per capire meglio come funziona, useremo un esempio. Ogni volta che viene toccato un nodo, lo rimuoviamo dalla scena. Aggiungi il seguente snippet di codice al file viewDidLoad metodo del ViewController classe:

override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) lascia tapRecognizer = UITapGestureRecognizer () tapRecognizer.numberOfTapsRequired = 1 tapRecognizer .numberOfTouchesRequired = 1 tapRecognizer.addTarget (self, action: "sceneTapped:") sceneView.gestureRecognizers = [tapRecognizer] ...

Quindi, aggiungere il seguente metodo al ViewController classe:

func sceneTapped (recognizer: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lascia hitResults = sceneView.hitTest (posizione, opzioni: nil) se hitResults? .count> 0 let result = hitResults! [0] as! SCNHitTestResult let node = result.node node.removeFromParentNode ()

In questo metodo, per prima cosa ottieni la posizione del rubinetto come a CGPoint. Successivamente, si utilizza questo punto per eseguire un hit test su sceneView oggetto e memorizzare il SCNHitTestResult oggetti in un array chiamato hitResults. Il opzioni il parametro di questo metodo può contenere un dizionario di chiavi e valori, che puoi leggere nella documentazione di Apple. Verifichiamo quindi se il hit test ha restituito almeno un risultato e, in tal caso, rimuoviamo il primo elemento nell'array dal suo nodo genitore.

Se l'hit test ha restituito più risultati, gli oggetti vengono ordinati in base alla loro posizione z, ovvero l'ordine in cui vengono visualizzati dal punto di vista della videocamera corrente. Ad esempio, nella scena corrente, se tocchi una delle due sfere o il pulsante, il nodo che hai toccato formerà il primo elemento dell'array restituito. Poiché il terreno appare direttamente dietro questi oggetti dal punto di vista della telecamera, tuttavia, il nodo di terra sarà un altro elemento nella serie di risultati, il secondo in questo caso. Questo accade perché un colpetto nella stessa posizione colpirebbe il nodo di terra se le sfere e il pulsante non erano lì.

Crea ed esegui la tua app e tocca gli oggetti nella scena. Dovrebbero scomparire man mano che li colpisci.

Ora che possiamo determinare quando viene toccato un nodo, possiamo iniziare ad aggiungere animazioni al mix.

3. Animazione

Esistono due classi che possono essere utilizzate per eseguire animazioni in SceneKit:

  • SCNAction
  • SCNTransaction

SCNAction gli oggetti sono molto utili per animazioni semplici e riusabili, come movimento, rotazione e scala. È possibile combinare qualsiasi numero di azioni insieme in un oggetto azione personalizzato.

Il SCNTransaction la classe può eseguire le stesse animazioni, ma è più versatile in alcuni modi, come ad esempio l'animazione dei materiali. Questa maggiore versatilità, tuttavia, ha un costo di SCNTransaction le animazioni hanno solo la stessa riusabilità di una funzione e l'impostazione viene eseguita tramite i metodi di classe.

Per la tua prima animazione, ti mostrerò il codice usando entrambi SCNActionSCNTransaction classi. L'esempio sposta il pulsante verso il basso e lo rende bianco quando viene toccato. Aggiorna l'implementazione di sceneTapped (_ :) metodo come mostrato di seguito.

func sceneTapped (recognizer: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lascia hitResults = sceneView.hitTest (posizione, opzioni: nil) se hitResults? .count> 0 let result = hitResults! [0] as! SCNHitTestResult let node = result.node if node == button SCNTransaction.begin () SCNTransaction.setAnimationDuration (0.5) lascia materiali = node.geometry? .Materials as! [SCNMaterial] let material = materials [0] material.diffuse.contents = UIColor.whiteColor () SCNTransaction.commit () lascia azione = SCNAction.moveByX (0, y: -0.8, z: 0, durata: 0.5) nodo. runAction (azione)

Nel sceneTapped (_ :) metodo, otteniamo un riferimento al nodo che l'utente ha toccato e controlla se questo è il pulsante nella scena. Se lo è, animiamo il suo materiale da rosso a bianco, usando il SCNTransaction classe, e spostarlo lungo l'asse y in direzione negativa usando un SCNAction esempio. La durata dell'animazione è impostata su 0,5 secondi.

Crea ed esegui nuovamente la tua app e tocca il pulsante. Dovrebbe spostarsi verso il basso e cambiare il suo colore in bianco, come mostrato nello screenshot qui sotto.

4. Fisica

L'impostazione di simulazioni fisiche realistiche è facile con il framework SceneKit. La funzionalità offerta dalle simulazioni fisiche di SceneKit è ampia, che va dalle velocità di base, alle accelerazioni e alle forze, ai campi gravitazionali e elettrici e persino al rilevamento delle collisioni.

Quello che stai per fare nella scena corrente è applicare un campo gravitazionale a una delle sfere in modo che la seconda sfera venga tirata verso la prima sfera come risultato della gravità. Questa forza di gravità diventerà attiva quando si preme il pulsante.

L'installazione per questa simulazione è molto semplice. Usa un SCNPhysicsBody oggetto per ogni nodo a cui si desidera essere interessati dalla simulazione fisica e a SCNPhysicsField oggetto per ogni nodo che vuoi essere la fonte di un campo. Aggiorna il viewDidLoad metodo come mostrato di seguito.

override func viewDidLoad () ... buttonGeometry.materials = [buttonMaterial] button = SCNNode (geometry: buttonGeometry) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) // Physics let groundShape = SCNPhysicsShape (geometry: groundGeometry, options: nil) let groundBody = SCNPhysicsBody (tipo: .Kinematic, shape: groundShape) ground.physicsBody = groundBody let gravityField = SCNPhysicsField.radialGravityField () gravityField.strength = 0 sphere1.physicsField = gravityField lascia shape = SCNPhysicsShape (geometry: sphereGeometry, options: nil) let sphere1Body = SCNPhysicsBody (tipo: .Kinematic, shape: shape) sphere1.physicsBody = sphere1Body let sphere2Body = SCNPhysicsBody (tipo: .Dynamic, shape: shape) sphere2.physicsBody = sphere2Body sceneView.scene? .rootNode .addChildNode (self.camera) sceneView.scene? .rootNode.addChildNode (ground) sceneView.scene? .rootNode.addChildNode (light) ...

Iniziamo creando un SCNPhysicsShape istanza che specifica la forma effettiva dell'oggetto che prende parte alla simulazione fisica. Per le forme di base che stai utilizzando in questa scena, gli oggetti geometrici sono perfettamente adatti all'uso. Per i modelli 3D complicati, tuttavia, è meglio combinare più forme primitive per creare una forma approssimativa dell'oggetto per la simulazione fisica.

Da questa forma, quindi crei un SCNPhysicsBody istanza e aggiungerla al terreno della scena. Questo è necessario, perché ogni scena di SceneKit ha per impostazione predefinita un campo di gravità esistente che trascina verso il basso ogni oggetto. Il cinematica tipo che dai a questo SCNPhysicsBody significa che l'oggetto prenderà parte alle collisioni, ma non è influenzato dalle forze (e non cadrà a causa della gravità).

Successivamente, si crea il campo gravitazionale e lo si assegna al primo nodo della sfera. Seguendo lo stesso processo del terreno, crei un corpo fisico per ciascuna delle due sfere. Si specifica la seconda sfera come a Dinamico corpo fisico, però, perché vuoi che sia influenzato e mosso dal campo gravitazionale che hai creato.

Infine, è necessario impostare la forza di questo campo per attivarlo quando si tocca il pulsante. Aggiungi la seguente riga al sceneTapped (_ :) metodo:

func sceneTapped (recognizer: UITapGestureRecognizer) ... if node == button ... sphere1.physicsField? .strength = 750

Crea ed esegui la tua app, tocca il pulsante e osserva come la seconda sfera accelera lentamente verso la prima. Nota che potrebbero passare alcuni secondi prima che la seconda sfera inizi a muoversi.

C'è solo una cosa da fare, tuttavia, far esplodere le sfere quando si scontrano.

5. Rilevamento di collisioni e sistemi di particelle

Per creare l'effetto di un'esplosione, faremo leva sul SCNParticleSystem classe. Un sistema di particelle può essere creato da un programma 3D esterno, da un codice sorgente o, come sto per mostrarvi, dall'editor di sistema di particelle di Xcode. Crea un nuovo file premendo Comando + N e scegliere SceneKit Particle System dal iOS> Risorsa sezione.

Impostare il modello del sistema di particelle su Reattore.Clic Il prossimo, nominare il file Esplosione, e salvarlo nella cartella del progetto.

Nel Project Navigator, ora vedrai due nuovi file, Explosion.scnpspark.png. Il spark.png l'immagine è una risorsa utilizzata dal sistema di particelle, automaticamente aggiunta al tuo progetto. Se apri Explosion.scnp, lo vedrai animato e reso in tempo reale su Xcode. L'editor di sistema di particelle è uno strumento molto potente in Xcode e consente di personalizzare un sistema di particelle senza doverlo fare a livello di programmazione. 

Con il sistema di particelle aperto, andare al Ispettore degli attributi a destra e cambia i seguenti attributi in Emettitore sezione:

  • Tasso di natalità a 300
  • Modalità di direzione Casuale

Cambia i seguenti attributi in Simulazione sezione:

  • Durata a 3
  • Fattore di velocità a 2

E infine, cambia i seguenti attributi in Ciclo vitale sezione:

  • Emissione dur. a 1
  • looping Riproduce una volta

Il tuo sistema di particelle dovrebbe ora sparare in tutte le direzioni e apparire simile allo screenshot seguente:

Aperto ViewController.swift e fai il tuo ViewController classe conforme al SCNPhysicsContactDelegate protocollo. L'adozione di questo protocollo è necessaria per rilevare una collisione tra due nodi.

class ViewController: UIViewController, SCNPhysicsContactDelegate

Quindi, assegna la corrente ViewController istanza come il contactDelegate del tuo physicsWorld oggetto nel viewDidLoad metodo.

override func viewDidLoad () super.viewDidLoad () sceneView = SCNView (frame: self.view.frame) sceneView.scene = SCNScene () sceneView.scene? .physicsWorld.contactDelegate = self self.view.addSubview (sceneView) ...

Infine, implementa il physicsWorld (_: didUpdateContact :) metodo nel ViewController classe:

func physicsWorld (world: SCNPhysicsWorld, didUpdateContact contact: SCNPhysicsContact) if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) let particleSystem = SCNParticleSystem (denominato: "Explosion", inDirectory: nil) let systemNode = SCNNode () systemNode.addParticleSystem (particleSystem) systemNode.position = contact.nodeA.position sceneView.scene? .RootNode.addChildNode (systemNode) contact.nodeA.removeFromParentNode () contact.nodeB.removeFromParentNode ()

Per prima cosa controlliamo se i due nodi coinvolti nella collisione sono le due sfere. In tal caso, carichiamo il sistema di particelle dal file che abbiamo creato un momento fa e lo aggiungiamo a un nuovo nodo. Alla fine, rimuoviamo entrambe le sfere coinvolte nella collisione dalla scena.

Crea ed esegui nuovamente la tua app e tocca il pulsante. Quando le sfere entrano in contatto, dovrebbero sparire e il sistema di particelle dovrebbe apparire e animarsi.

Conclusione

In questo tutorial, ti ho mostrato come implementare l'interazione dell'utente, l'animazione, la simulazione fisica e i sistemi di particelle utilizzando il framework SceneKit. Le tecniche che hai imparato in questa serie possono essere applicate a qualsiasi progetto con un numero qualsiasi di animazioni, simulazioni fisiche, ecc.

Ora dovresti essere a tuo agio nel creare una scena semplice e aggiungere elementi dinamici ad essa, come i sistemi di animazione e di particelle. I concetti che hai appreso in questa serie sono applicabili alla scena più piccola con un singolo oggetto fino ad un gioco su larga scala.