O introducere în SceneKit interacțiunea cu utilizatorul, animațiile și fizica

Ce veți crea

Aceasta este a doua parte a seriei noastre introductive despre SceneKit. În acest tutorial, presupun că sunteți familiarizați cu conceptele explicate în prima parte, inclusiv crearea unei scene cu lumini, umbre, camere, noduri și materiale.

În acest tutorial, vă voi învăța despre unele dintre caracteristicile mai complicate, dar și mai utile ale SceneKit, cum ar fi animația, interacțiunea cu utilizatorul, sistemele de particule și fizica. Prin implementarea acestor funcții, puteți crea conținut 3D interactiv și dinamic mai degrabă decât obiecte statice, cum ați făcut în tutorialul anterior.

1. Configurarea scenei

Creați un nou proiect Xcode bazat pe iOS> Application> Application Single View șablon.

Denumiți proiectul, setați Limba la Rapid, și Dispozitive la universal.

Deschis ViewController.swift și importați cadrul SceneKit.

import import SceneKit

Apoi, declarați următoarele proprietăți în ViewController clasă.

var sceneView: SCNView! Var camera: SCNNode! var teren: SCNNode! var lumina: SCNNod! buton var: SCNNode! var sfera1: SCNNod! var sfera2: SCNNod!

Am creat scena în viewDidLoad așa cum se arată mai jos.

override funcția viewDidLoad () super.viewDidLoad () sceneView = SCNView (cadru: self.view.frame) scenaView.scene = SCNScene () self.view.addSubview (sceneView) lasă groundGeometry = SCNFloor () groundGeometry.reflectivity = materialul = SCNMaterial () groundMaterial.diffuse.contents = UIColor.blueColor () groundGeometry.materials = sol GroundMaterial = SCNNode (geometria: groundGeometry) let camera = SCNCamera () camera.zFar = 10000 self.camera = .camera.camera = camera auto.camera.position = SCNVector3 (x: -20, y: 15, z: 20) let constraint = SCNLookAtConstraint (țintă: sol) constraint.gimbalLockEnabled = true self.camera.constraints = [constraint] lăsați ambientLight = SCNLight () ambientLight.color = UIColor.darkGrayColor () ambientLight.type = SCNLightTypeAmbient auto.camera.light = ambientLight lasă spotLight = SCNLight () spotLight.type = SCNLightTypeSpot spotLight.castsShadow = adevărat spotLight.spotInnerAngle = 70.0 spotLight. spotOuterAngle = 90.0 spotLight.zFar = 500 l ()) = () = () = () = (=) (= sphereMaterial.diffuse.contents = UIColor.greenColor () sphereGeometry.materials = [sphereMaterial] sphere1 = SCNNode (geometrie: sphereGeometrie) sphere1.position = SCNVector3 (x: -15, y: 1.5, z: 0) : sphereGeometrie) sfera2.poziția = SCNVector3 (x: 15, y: 1.5, z: 0) săgeatăGeometrie = SCNBox (lățime: 4, înălțimea: 1, lungimea: 4, chamferRadius: 0) buttonMaterial = SCNMaterial diffuse.contents = butonul UICcolor.redColor ()Geometry.materials = butonul [buttonMaterial] = butonul SCNNode (geometria: butonulGeometrie) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) sceneView.scene? .rootNode.addChildNode (auto.camera) sceneView.scene? .rootNode.addChildNode (la sol) sceneView.scene? .rootNode.addChildNode (lumina) sceneView.scene? .rootNode.addChildNode (buton) sceneView.scen e? .rootNode.addChildNode (sfera1) sceneView.scene? .rootNode.addChildNode (sfera2)

Implementarea sistemului viewDidLoad ar trebui să pară familiar dacă ați citit prima parte a acestei serii. Tot ce facem este crearea scenei pe care o vom folosi în acest tutorial. Singurele lucruri noi includ SCNFloor clasa și zFar proprietate.

După cum sugerează și numele, SCNFloor clasa este folosită pentru a crea o pardoseală sau un teren pentru scenă. Acest lucru este mult mai ușor comparativ cu crearea și rotirea unui SCNPlane așa cum am făcut în tutorialul anterior.

 zFar proprietatea determină cât de departe se poate vedea o cameră sau cât de departe poate ajunge o lumină dintr-o anumită sursă. Construiți și executați aplicația. Scena dvs. ar trebui să arate cam așa:

2. Interacțiunea cu utilizatorul

Interacțiunea cu utilizatorul este tratată în SceneKit printr-o combinație a UIGestureRecognizer clasa și testele de succes. Pentru a detecta un robinet, de exemplu, adăugați mai întâi a UITapGestureRecognizer la a SCNView, determinați poziția robinetului în vizualizare și vedeți dacă acesta este în contact sau atinge unul dintre noduri.

Pentru a înțelege mai bine cum funcționează acest lucru, vom folosi un exemplu. Ori de câte ori este folosit un nod, îl eliminăm din scenă. Adăugați următorul fragment de cod în secțiunea viewDidLoad metodă a ViewController clasă:

override funcția viewDidLoad () super.viewDidLoad () scenaView = SCNView (cadru: self.view.frame) sceneView.scene = SCNScene () self.view.addSubview (sceneView) permite tapRecognizer = UITapGestureRecognizer () tapRecognizer.numberOfTapsRequired = 1 tapRecognizer .numberOfTouchesRequired = 1 tapRecognizer.addTarget (auto, acțiune: "sceneTapped:") sceneView.gestureRecognizers = [tapRecognizer] ...

Apoi, adăugați următoarea metodă la ViewController clasă:

Funcționează cu funcția Tutted (recunoaștere: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lăsați hitResults = sceneView.hitTest (locație, opțiuni: nil) dacă hitResults? .count> 0 let rezultat = hitResults! SCNHitTestResult permite node = result.node node.removeFromParentNode ()

În această metodă, mai întâi obțineți locația robinetului ca a CGPoint. Apoi, utilizați acest punct pentru a efectua un test de succes pe sceneView obiect și stocați SCNHitTestResult obiecte dintr-o matrice numită hitResults. Opțiuni parametrul acestei metode poate conține un dicționar de chei și valori, despre care puteți citi în documentația Apple. Apoi, verificăm dacă testul de salvare a returnat cel puțin un rezultat și, dacă a făcut-o, vom elimina primul element din matrice din nodul său parental.

Dacă testul de salvare a returnat mai multe rezultate, obiectele sunt sortate după poziția lor z, adică ordinea în care acestea apar din punctul de vedere al camerei curente. De exemplu, în scena curentă, dacă atingeți una dintre cele două sfere sau butonul, nodul pe care l-ați lovit va forma primul element din matricea returnată. Deoarece pământul apare direct în spatele acestor obiecte din punctul de vedere al camerei, cu toate acestea, nodul de sol va fi un alt element din matricea rezultatelor, al doilea în acest caz. Acest lucru se întâmplă deoarece un robinet în aceeași locație ar lovi nodul de bază dacă sferele și butonul nu erau acolo.

Construiți și executați aplicația dvs. și atingeți obiectele din scenă. Ar trebui să dispară pe măsură ce atingi pe fiecare.

Acum, când putem determina momentul când un nod este atins, putem începe să adăugăm animații în mix.

3. Animație

Există două clase care pot fi folosite pentru a realiza animații în SceneKit:

  • SCNAction
  • SCNTransaction

SCNAction obiectele sunt foarte utile pentru animații simple și reutilizabile, cum ar fi mișcarea, rotația și scala. Puteți combina orice număr de acțiuni împreună într-un obiect de acțiune personalizat.

 SCNTransaction clasa poate realiza aceleași animații, dar este mai versatilă în anumite moduri, cum ar fi materialele de animație. Această versatilitate adăugată, totuși, vine cu costul SCNTransaction animațiile având numai aceeași reutilizabilitate ca funcție și setarea se face prin metode de clasă.

Pentru prima animație, vă voi arăta cod utilizând ambele SCNAction și SCNTransaction clase. Exemplul vă va mișca butonul în jos și îl veți albi atunci când este apăsat. Actualizați punerea în aplicare a sceneTapped (_ :) așa cum se arată mai jos.

Funcționează cu funcția Tutted (recunoaștere: UITapGestureRecognizer) let location = recognizer.locationInView (sceneView) lăsați hitResults = sceneView.hitTest (locație, opțiuni: nil) dacă hitResults? .count> 0 let rezultat = hitResults! SCNHitTestResult permite node = result.node dacă nod == butonul SCNTransaction.begin () SCNTransaction.setAnimationDuration (0.5) permite materiale = node.geometry? .Materials as! [SCNMaterial] permite material = materiale [0] material.diffuse.contents = UIColor.whiteColor () SCNTransaction.commit () permite acțiunea = nodul SCNAction.moveByX (0, y: -0.8, z: 0, duration: 0.5). runAction (acțiune)

În sceneTapped (_ :) , obținem o referință la nodul pe care utilizatorul la atins și verifică dacă acesta este butonul din scenă. Dacă este, îi animăm materialul de la roșu la alb, folosind SCNTransaction și mutați-o de-a lungul axei y într-o direcție negativă, folosind o SCNAction instanță. Durata animației este setată la 0,5 secunde.

Construiți și rulați din nou aplicația dvs. și apăsați pe buton. Ar trebui să se mute în jos și să-și schimbe culoarea în alb, după cum se arată în captura de ecran de mai jos.

4. Fizica

Configurarea simulărilor fizice realiste este facilă cu cadrul SceneKit. Funcționalitatea oferită de simulările de fizică ale SceneKit este extinsă, de la viteze de bază, accelerații și forțe, până la câmpuri gravitaționale și electrice și chiar detectarea coliziunilor.

Ceea ce veți face în scena actuală este să aplicați un câmp gravitațional uneia dintre sfere astfel încât a doua sferă să fie trasă spre prima sferă ca urmare a gravitației. Această forță de gravitație va deveni activă când butonul este apăsat.

Setarea pentru această simulare este foarte simplă. Utilizați un SCNPhysicsBody obiect pentru fiecare nod pe care doriți să fiți afectat de simulare fizică și o SCNPhysicsField obiect pentru fiecare nod pe care doriți să fie sursa unui câmp. Actualizați viewDidLoad așa cum se arată mai jos.

override funcția viewDidLoad () ... butonulGeometry.materials = butonul [buttonMaterial] = SCNNode (geometria: butonulGeometrie) button.position = SCNVector3 (x: 0, y: 0.5, z: 15) // Physics let groundShape = SCNPhysicsShape (geometria: (geometrie: geometria: geometria: geometria: geometria: geometria: geometria: geometria: geometria: sferaSefa geometrica, optiuni: nil) lasa sphere1Body = SCNPhysicsBody (tip: .Kinematic, forma: forma) sfera1.physicsBody = sfera1Spareaza sfera2Body = SCNPhysicsBody (tip: .Dynamic, shape: shape) sphere2.physicsBody = sphere2Body sceneView.scene? .rootNode .adotChildNode (auto.camera) sceneView.scene? .rootNode.addChildNode (la sol) sceneView.scene? .rootNode.addChildNode (lumina) ...

Începem prin crearea unui SCNPhysicsShape exemplu care specifică forma reală a obiectului care participă la simularea fizică. Pentru formele de bază pe care le utilizați în această scenă, obiectele de geometrie sunt perfect fine de utilizat. Pentru modele 3D complicate, cu toate acestea, este mai bine să combinați mai multe forme primitive împreună pentru a crea o formă aproximativă a obiectului dvs. pentru simulare fizică.

Din această formă, creați un SCNPhysicsBody instanță și adăugați-o la solul scenei. Acest lucru este necesar, deoarece fiecare scenă SceneKit are implicit un câmp de gravitație existent care trage fiecare obiect în jos. Cinematic tipul pe care îl oferiți acest lucru SCNPhysicsBody înseamnă că obiectul va lua parte la coliziuni, dar nu este afectat de forțe (și nu va cădea din cauza gravitației).

Apoi, creați câmpul gravitațional și alocați-l acestui prim nod sferic. Urmând același proces ca și în cazul terenului, creați apoi un corp fizic pentru fiecare dintre cele două sfere. Specificați a doua sferă ca a Dinamic fizic, pentru că doriți să fie afectată și mișcată de câmpul gravitațional pe care l-ați creat.

În cele din urmă, trebuie să setați puterea acestui câmp pentru a-l activa atunci când este apăsat butonul. Adăugați următoarea linie la sceneTapped (_ :) metodă:

func sceneTapped (recunoaștere: UITapGestureRecognizer) ... dacă nod == butonul ... sphere1.physicsField? .strength = 750

Construiți și rulați aplicația dvs., atingeți butonul și urmăriți, deoarece cea de-a doua sferă se accelerează încet spre prima. Rețineți că poate dura câteva secunde înainte ca a doua sferă să înceapă să se miște.

Este totuși un singur lucru pe care trebuie să-l acoperiți, totuși, sferele explodează atunci când se ciocnesc.

5. Detectarea coliziunilor și sistemele de particule

Pentru a crea efectul unei explozii, vom folosi efectul SCNParticleSystem clasă. Un sistem de particule poate fi creat printr-un program 3D extern, un cod sursă sau, așa cum am de gând să vă arăt, editorul de sistem al particulelor Xcode. Creați un fișier nou apăsând Comandă + N și alegeți Sistemul de particule SceneKit de la iOS> Resurse secțiune.

Setați șablonul sistemului de particule la Reactor.Clic Următor →, numele fișierului Explozie, și salvați-l în dosarul proiectului.

În Project Navigator, veți vedea acum două fișiere noi, Explosion.scnp și spark.png.  spark.png imaginea este o resursă utilizată de sistemul de particule, adăugată automat în proiectul dvs. Dacă vă deschideți Explosion.scnp, veți vedea că este animat și redat în timp real în Xcode. Editorul de sisteme de particule este un instrument foarte puternic în Xcode și vă permite să personalizați un sistem de particule fără a trebui să o faceți programabil. 

Cu sistemul de particule deschis, mergeți la Atribuții Inspector din dreapta și modificați următoarele atribute în emiţător secțiune:

  • Rata de nastere la 300
  • Direcție mod la Întâmplător

Modificați următoarele atribute în Simulare secțiune:

  • Durata de viata la 3
  • Factor de viteză la 2

În final, schimbați următoarele atribute în Ciclu de viață secțiune:

  • Emisie dur. la 1
  • luping la Joacă o dată

Sistemul dvs. de particule ar trebui să tragă acum în toate direcțiile și să semene cu următoarea imagine:

Deschis ViewController.swift și faceți-vă ViewController clasa este conformă cu SCNPhysicsContactDelegate protocol. Adoptarea acestui protocol este necesară pentru a detecta o coliziune între două noduri.

clasa ViewController: UIViewController, SCNPhysicsContactDelegate

Apoi, atribuiți curentul ViewController exemplu ca contactDelegate de dumneavoastră physicsWorld obiect în viewDidLoad metodă.

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

În cele din urmă, pune în aplicare physicsWorld (_: didUpdateContact :) metodă în ViewController clasă:

functie fiziceWorld (lume: SCNPhysicsWorld, contacte didUpdateContact: SCNPhysicsContact) if (contact.nodeA == sphere1 || contact.nodeA == sphere2) && (contact.nodeB == sphere1 || contact.nodeB == sphere2) lăsa particleSystem = SCNParticleSystem (numit: "Explosion", inDirectory: nil) permite sistemNode = SCNNode () systemNode.addParticleSystem (particleSystem) systemNode.position = contact.nodeA.position sceneView.scene ?.rootNode.addChildNode (systemNode) contact.nodeA.removeFromParentNode () contact.nodeB.removeFromParentNode ()

Mai întâi verificăm dacă cele două noduri implicate în coliziune sunt cele două sfere. Dacă este cazul, atunci încărcăm sistemul de particule din fișierul pe care l-am creat acum un moment și îl adăugăm unui nou nod. În cele din urmă, eliminăm ambele sfere implicate în coliziunea din scenă.

Construiți și rulați din nou aplicația și atingeți butonul. Când sferele intră în contact, ambele ar trebui să dispară și sistemul vostru de particule ar trebui să apară și să animeze.

Concluzie

În acest tutorial, v-am arătat cum să implementați interacțiunea cu utilizatorul, animația, simularea fizică și sistemele de particule folosind cadrul SceneKit. Tehnicile pe care le-ați învățat în această serie pot fi aplicate pentru orice proiect cu orice număr de animații, simulări fizice etc..

Acum ar trebui să vă simțiți confortabil creând o scenă simplă și adăugând elemente dinamice, cum ar fi sistemele de animație și particule. Conceptele pe care le-ați învățat în această serie sunt aplicabile celei mai mici scene cu un singur obiect până la un joc la scară largă.

Cod