HIT해

[iOS/SceneKit] 특정 노드 액션 구현하기 본문

Swift/Swift 개발 노트

[iOS/SceneKit] 특정 노드 액션 구현하기

힛해 2024. 9. 3. 01:14
728x90

https://developer.apple.com/documentation/scenekit/scnaction

 

SCNAction | Apple Developer Documentation

A simple, reusable animation that changes attributes of any node you attach it to.

developer.apple.com

 

SceneView에서 보여주고 있는 모델 안의 노드들중 특정 노드를 클릭했을때 다른 반응을 연출해야하는 상황이 있었다.

 

이를 해결하기위해 두가지 방법을 생각했다.

  1. 모델별 SceneView를 불라와서 ZStack으로 감싼다.
  2. 하나의 SceneView를 불러오고 모델속 노드 터치를 감지한다.

 

1의 방법이 구현은 더 쉽겠지만 ZStack으로 감쌋을때 뒤에 있는 뷰에 제스처 접근이 어렵다고 생각되어 2번 방법으로 진행했다.

 

Coordinator 클래스의 UITapGestureRecognizer를 사용하여 터치 이벤트를 처리하고.

handleTapGesture 메서드에 감지된 터치 위치를 기반으로 SCNView에서 SCNNode를 탐색했다.

class Coordinator: NSObject {
        var parent: DolView
        
        init(parent: DolView) {
            self.parent = parent
        }
        
        @objc func handleTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
            let scnView = gestureRecognizer.view as! SCNView
            let location = gestureRecognizer.location(in: scnView)
            let hitResults = scnView.hitTest(location, options: nil)
            
            if let hitResult = hitResults.first {
                let touchedNode = hitResult.node

 

이렇게 구현했더니 놀랍게도 3D 모델의 각 노드 조그마한 부분 하나 하나를 모두 인식했다.

 

하지만 너무 잘 인식해서 문제였다.

 

그 이유는 얼굴(눈,코,입) 이 있다면 나는 얼굴 전체 노드에 액션을 주고 싶은데

 

얼굴 부분들중 눈을 눌렀을때는 touchedNode에는 눈 노드가 인식되는 것이었다.

 

( 물론 모델 요소 하나하나가 큰 노드라면 상관 없겠지만 내가 진행하고 있는 토이 프로젝트는 눈, 코, 입 모두 커스텀 가능한 요소라 눈 아래에도 작은 노드가 들어있다... )

 

그래서 터치된 노드의 최상위 부모 노드를 불러와 부모 노드에 액션을 주는 방식으로 해결했다.

 

if let hitResult = hitResults.first {
                let touchedNode = hitResult.node
                
                print("touchNode ", touchedNode)
                
                // 노드의 이름을 기반으로 터치된 노드를 확인합니다.
                if let parentNode = touchedNode.parent, parentNode.name == "부모노드이름" {
                    print("터치된 노드의 부모 노드가 ... 입니다.")
                    parent.state = true
                }
                
                if let parentNode = touchedNode.parent, parentNode.name == "부모노드이름" {
                    let moveAction = SCNAction.moveBy(x: 0, y: 0, z: 3, duration: 1)
                    parentNode.runAction(moveAction)
                }

 

이렇게 하니 작은 노드 단위 하나하나가 아닌 부모노드를 움직여 원하는 대로 구현이 됐다.

 

원래는 펫말을 눌렀을때 팝업창이 뜨는 기능만 만들려고 했지만 SCNAction 공식문서를 보니 재미있는 기능이 있길래 만들어보았다.

 

https://developer.apple.com/documentation/scenekit/scnaction

 

SCNAction | Apple Developer Documentation

A simple, reusable animation that changes attributes of any node you attach it to.

developer.apple.com

 

let rotateAction1 = SCNAction.rotate(by: -2 * .pi , around: SCNVector3(0, 0, 1), duration: 3)
                    let moveAction1 = SCNAction.moveBy(x: 4, y: 0, z: 0, duration: 3)
                    
                    let actionGroup1 = SCNAction.group([rotateAction1, moveAction1])
                    let actionGroup2 = actionGroup1.reversed()
                    
                    let reverseMoveSequence = SCNAction.sequence([actionGroup1, actionGroup2])
                    
                    parentNode.runAction(reverseMoveSequence)

 

모델을 굴리거나 점프시키거나

 

reverse 함수를 사용하면 단일,그룹 액션 설정값의 반대 액션으로 손쉽게 구현할 수 있어 왕복 행동도 쉽게 만들 수 있었다.

 

구현 모습

 

 

 

점점 SCN 고수가 되어가는 것 같다...