scaling a SCNNode in runtime using the Pinch gestu

2020-07-18 11:15发布

问题:

I am trying to scale and SCNNode in real time using the Pinch gesture:

This is my current code

let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(from:)))
sceneView.addGestureRecognizer(pinchGestureRecognizer)

@objc
func handlePinch(from recognizer: UIPinchGestureRecognizer){
  var pinchScale = recognizer.scale
  pinchScale = round(pinchScale * 1000) / 1000.0

  sceneView.scene.rootNode.enumerateChildNodes { (node, stop) -> Void in
    if(node.name == "Box01"){
       node.scale = SCNVector3(x: pinchScale, y: pinchScale, z: pinchScale)
    }
  }        
}

However the node doesn't scale big or small? Can someone please point my mistake?

The SCNNode is loaded and has an animation on applied like so,

sceneView.scene.rootNode.addChildNode(node)
loadAnimation(animation: .Attack, sceneName: "art.scnassets/attack", animationIdentifier: "attackID");

回答1:

Got it to work in swift

@objc func handlePinch(gesture: UIPinchGestureRecognizer){
    if(scnnodeSelected){

        if (gesture.state == .changed) {
            let pinchScaleX = Float(gesture.scale) * tappedObjectNode.scale.x
            let pinchScaleY =  Float(gesture.scale) * tappedObjectNode.scale.y
            let pinchScaleZ =  Float(gesture.scale) * tappedObjectNode.scale.z
            tappedObjectNode.scale = SCNVector3(pinchScaleX, pinchScaleY, pinchScaleZ)
            gesture.scale=1
        }
    }
}


回答2:

func addPinchGestureToSceneView(){
    let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.handlePitch(withGestureRecognizer:)))
    sceneView.addGestureRecognizer(pinchGestureRecognizer)
}


@objc func handlePitch(withGestureRecognizer recognizer: UIPinchGestureRecognizer) {
    let tapRecognizer = recognizer.location(in: sceneView)
    let hitTestResults = sceneView.hitTest(tapRecognizer)
    guard let node = hitTestResults.first?.node else {
        return
    }

    if (recognizer.state == .changed) {
        let pinchScaleX = Float(recognizer.scale) * node.scale.x
        let pinchScaleY =  Float(recognizer.scale) * node.scale.y
        let pinchScaleZ =  Float(recognizer.scale) * node.scale.z

        node.scale = SCNVector3(x: Float(pinchScaleX), y: Float(pinchScaleY), z: Float(pinchScaleZ))
        recognizer.scale=1
    }

}


回答3:

I used this to handle the scale:

@objc
func handlePinch(from recognizer: UIPinchGestureRecognizer){
    var pinchScale = round(recognizer.scale * 1000)/1000000
    let node_arm = sceneView.scene.rootNode.childNode(withName: "army", recursively: true)
    node_arm?.runAction(.customAction(duration: 0, action: { node, progress in
        node.physicsBody = nil
        node.scale = SCNVector3(x: Float(pinchScale), y: Float(pinchScale), z: Float(pinchScale))
    }))
}


回答4:

I've found that this works pretty well. I also use both touches to try and detect if I'm pinching on an object or not, so you can grab the SCNNode with either finger.

Note, the line that resets the scale is necessary, otherwise it acts really buggy. I can't particularly say why that it is the case though.

-(void)scaleObject:(UIPinchGestureRecognizer *)recognizer {
    if (recognizer.state == UIGestureRecognizerStateBegan) {

        CGPoint tapPoint = [recognizer locationOfTouch:1 inView:_sceneView]; //Get tap location on the screen from the 2nd touch
        NSArray <SCNHitTestResult *> *result = [self.sceneView hitTest:tapPoint options:nil]; //Get result array, checks on if we hit a SceneNode or not

        if ([result count] == 0) { //If the first touch doesn't grap the SceneNode, try the second touch
            tapPoint = [recognizer locationOfTouch:0 inView:_sceneView];
            result = [self.sceneView hitTest:tapPoint options:nil]; // Get the results
            if ([result count] == 0) {
                return; //No objects found, return
            }
        }

        SCNHitTestResult *hitResult = [result firstObject]; //Get the first hitResult
        scaledObject = [[hitResult node] parentNode];
        if (scaledObject) {
            [NotificationView showNotificationWithText:@"Object has been selected for pinch"];
        }
    }
    if (recognizer.state == UIGestureRecognizerStateChanged) { //When pinch status is changing
        if (scaledObject) { //If we have an object grabbed
            CGFloat pinchScaleX = recognizer.scale * scaledObject.scale.x;
            CGFloat pinchScaleY = recognizer.scale * scaledObject.scale.y;
            CGFloat pinchScaleZ = recognizer.scale * scaledObject.scale.z;
            [scaledObject setScale:SCNVector3Make(pinchScaleX, pinchScaleY, pinchScaleZ)];
        }
        recognizer.scale = 1; //Reset the scale, skipping this line causes really weird behavior
    }
    if (recognizer.state == UIGestureRecognizerStateEnded) {
        NSLog(@"Done pinching");
        scaledObject = nil; //Make sure that no object is set to scaledObject
    }
}

Hope this helps



回答5:

Update for Swift 5 :

@objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {
    if (gesture.state == .changed) {
        yourSKNode.xScale *= gesture.scale
        yourSKNode.yScale *= gesture.scale
        gesture.scale = 1
    }
}

and my didMove function :

override func didMove(to view: SKView) {
    super.didMove(to: view)
    setupNodes()

    self.view!.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))))
}