2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【SceneKit】3Dアニメーションの切り替え(その2)

Last updated at Posted at 2020-04-19

はじめに

以前、ARKitでの3Dアニメーション切り替えについて書いたけど、別のやり方が見つかったので、そのメモ。今回はSceneKitベースだけど、基本的には同じです。

実行環境

やりたいこと

  • ボタンタップでアニメーションを切り替える。

3Dアニメーションの準備

MagicaVoxelで作ったモデルを Mixamo にアップロードして、いくつかのアニメーションを設定して個別にCollada(.dae)形式で書き出しておきます。
cap02.png
※このとき「In Place」のチェックボックスがあるものは、チェックしておくと同じ位置でアニメーションしてくれるので、切り替え時に位置がズレなくていいかもしれません。

cap01.png
※また、ベースとして使う .scn ファイルにはrootNode を作って名前を付けておきます。+ボタンでノードを追加して名前(今回は「idle」にしました)を付け、その下に「unamed」「mixamorig_Hips」を移動します。

3Dアニメーションの切り替え(差し替え)

以前の方法では、アニメーションを含む3Dのノード自体を差し替えていましたが、「3Dモデル(アニメーションつき)の再生速度を変更する」という記事を書いたときに、node.addAnimationPlayer で node のアニメーションを再設定できるということに気がついたので、その方法で切り替えてみました。

1.ベースにするDaeファイルを.scnに変換
2.上記1.の.scn から 3Dモデルのnodeを取り出す。
3.daeファイルから SCNAnimationPlayerr.loadAnimation でアニメーションを取り出す。
4.上記3.で取りだしたアニメーションを 上記2.のnodeに node.addAnimationPlayer で再設定する。

で、上記2.で取り出すアニメーションを事前に配列化しておいて、SegmentedControl が押されたときに、selectedIndexで配列から、アニメーションを取りだして再設定するようにします。

アニメーションの初期化
        let scene = SCNScene(named: "art.scnassets/idle.scn")!
        
        //アニメーション用配列;初期化
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Box Idle.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Cross Jumps Rotation.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Hip Hop Dancing.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Jazz Dancing.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Northern Soul Spin.dae"))

        danceControl.selectedSegmentIndex = 0
        
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 1, z: 5)
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        // ノード作成
        let node = scene.rootNode.childNode(withName: "idle", recursively: true)!
        
        //アニメーションを配列から取りだして再設定する
        danceAnimation = danceAnimationArray[danceControl.selectedSegmentIndex]
        
        //speed:1
        danceAnimation.speed = 1

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
        
        //スピード用スライダーの初期化
        initSlider(val: Float(danceAnimation!.speed))

        // アイテムの配置
        node.position = SCNVector3(0, 0, 0)
        node.scale = SCNVector3(1, 1, 1)
        scene.rootNode.addChildNode(node)

        // set the scene to the view
        self.mySceneView!.scene = scene

        // configure the view
        self.mySceneView!.backgroundColor = UIColor.clear

※初回は、scene を .scn ファイルから取り出し、SCNView(self.mySceneView) へ追加する。

アニメーションの切り替え
    //アニメーションの変更
    @IBAction func actionSegmentedControl(_ sender: UISegmentedControl) {
        print("actionSegmentedControl",sender.selectedSegmentIndex)
        
        // ノード作成
        let node = self.mySceneView.scene!.rootNode.childNode(withName: "idle", recursively: true)!
        
        //アニメーションを配列から取りだして再設定する
        danceAnimation = danceAnimationArray[sender.selectedSegmentIndex]
        danceAnimation.speed = CGFloat(speedSlider.value)

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.removeAllActions()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
        if (isPlay){
            danceAnimation.play()
        }
    }

※アニメーション切り替え時は、node を SCNView の scene(self.mySceneView.scene)から取り出し、その node へ アニメーションを追加する。

完成

SegmentedControlをタップすることで、割り当てられたアニメーションに切り替わります。

また前回の「【SceneKit】3Dモデル(アニメーションつき)の再生速度を変更する」をベースにしているため、再生ボタンでのアニメーションを再生/停止。スライダーでアニメーションの再生速度変更にも対応しています。

IMG_6181.PNG

ViewContoller.swift全体のコードです。

ViewContoller.swift
//
//  ViewController.swift
//  CahngeVariousDances
//
//  Created by c-geru on 2020/04/17.
//  Copyright © 2020 c-geru. All rights reserved.
//

import UIKit
import SceneKit

class ViewController: UIViewController {

    @IBOutlet weak var mySceneView: SCNView!
    
    @IBOutlet weak var playButton: UIBarButtonItem!
    
    @IBOutlet weak var speedSlider: UISlider!
    
    @IBOutlet weak var toolBar: UIToolbar!
    
    @IBOutlet weak var danceControl: UISegmentedControl!
    
    var danceAnimation: SCNAnimationPlayer!
    var isPlay: Bool = false
    
    var danceAnimationArray:[SCNAnimationPlayer?] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let scene = SCNScene(named: "art.scnassets/idle.scn")!
        
        //アニメーション用配列;初期化
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Box Idle.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Cross Jumps Rotation.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Hip Hop Dancing.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Jazz Dancing.dae"))
        danceAnimationArray.append(SCNAnimationPlayer.loadAnimation(fromSceneNamed: "art.scnassets/Northern Soul Spin.dae"))

        danceControl.selectedSegmentIndex = 0
        
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 1, z: 5)
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        //-----
        // ノード作成
        let node = scene.rootNode.childNode(withName: "idle", recursively: true)!
        
        //アニメーションを配列から取りだして再設定する
        danceAnimation = danceAnimationArray[danceControl.selectedSegmentIndex]
        
        //speed:1
        danceAnimation.speed = 1

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
        
        //スピード用スライダーの初期化
        initSlider(val: Float(danceAnimation!.speed))

        // アイテムの配置
        node.position = SCNVector3(0, 0, 0)
        node.scale = SCNVector3(1, 1, 1)
        scene.rootNode.addChildNode(node)

        // set the scene to the view
        self.mySceneView!.scene = scene

        // configure the view
        self.mySceneView!.backgroundColor = UIColor.clear
    }

    //スライダー:初期化
    func initSlider(val:Float){
        speedSlider.minimumValue = 0.1
        speedSlider.maximumValue = 3.0
        speedSlider.value = val
    }

    @IBAction func dragSlider(_ sender: UISlider) {
        
        print("+++value:",sender.value)
        
        // ノード作成
        let node = self.mySceneView.scene!.rootNode.childNode(withName: "idle", recursively: true)!
        
        //speed:2
        danceAnimation.speed = CGFloat(sender.value)

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.removeAllActions()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
    }
    
    @IBAction func touchUpSlider(_ sender: UISlider) {
        if (isPlay){
           danceAnimation.play()
        }
        
    }
    
    @IBAction func tapPlayButton(_ sender: UIBarButtonItem) {
        if (isPlay){
            danceAnimation.stop()
            isPlay = false
        } else {
            danceAnimation.play()
            isPlay = true
        }
        
        //開始ボタン:表示切替
        changePlayButton()
    }
    
    //アニメーションの変更
    @IBAction func actionSegmentedControl(_ sender: UISegmentedControl) {
        print("actionSegmentedControl",sender.selectedSegmentIndex)
        
        // ノード作成
        let node = self.mySceneView.scene!.rootNode.childNode(withName: "idle", recursively: true)!
        
        //アニメーションを配列から取りだして再設定する
        danceAnimation = danceAnimationArray[sender.selectedSegmentIndex]
        danceAnimation.speed = CGFloat(speedSlider.value)

        danceAnimation.stop() // stop it for now so that we can use it later when it's appropriate
        node.removeAllActions()
        node.addAnimationPlayer(danceAnimation, forKey: "dance")
        if (isPlay){
            danceAnimation.play()
        }
    }
    
    //開始ボタン:表示切替
    func changePlayButton() {
        var items = toolBar.items!
        var btnItem: UIBarButtonItem!

        if (isPlay) {
            print("----pause")
            btnItem = UIBarButtonItem(barButtonSystemItem: .pause, target: self, action: #selector(self.tapPlayButton(_:)))
        } else {
            print("----play")
            btnItem = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(self.tapPlayButton(_:)))
        }

        items[0] = btnItem
        toolBar.setItems(items, animated: false)
    }

}

extension SCNAnimationPlayer {
    class func loadAnimation(fromSceneNamed sceneName: String) -> SCNAnimationPlayer {
        let scene = SCNScene( named: sceneName )!
        // find top level animation
        var animationPlayer: SCNAnimationPlayer! = nil
        scene.rootNode.enumerateChildNodes { (child, stop) in
            if !child.animationKeys.isEmpty {
                animationPlayer = child.animationPlayer(forKey: child.animationKeys[0])
                stop.pointee = true
            }
        }
        return animationPlayer
    }
}

まとめ

これでdaeからアニメーションを取得して、切り替えることができるようになりました。
もっといい方法があるよ!という方はご指摘頂けるとありがたいです。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?