見出し画像

【アプリ開発日記24】3Dアニメーションをボタンで切り替える #three.js #blender

 動的なサイトやゲームを作るとき「3Dアニメーションいろいろ出せたらな」と思いながら5日くらい躓いてたので、できるようになったポイントを抜粋しながら振り返る、そんな回。

 参考にしたサイトと完成コードを感想はさみながら書いていきます。

完成イメージ

使ったもの

  • html,css,javascript

  • three.js(scene, 3D読み込みレベル)

  • blender(今回はglbで出力)

ローカル以外(ウェブやchrome拡張機能)でも使う場合

  • 上記 + webpack

流れ

1、単一アニメーションでいいので、3Dオブジェクトを読み込んで再生できるようにする

 出力形式は、困ったらglbでOK。

おすすめのサイト:

個人的に、日本語で一番わかりやすいThree.js入門サイト。

2、複数アニメーションをオブジェクトに登録する

 ただ複数作成するだけでは読み込みエラーになる、、、ので先に準備しておきましょう。blender内に、出力とは別の普通のアニメーションタブで設定します。

 NLAなんて機能があったとは。

おすすめサイト:

3、アニメーション切り替えの処理を書く(メイン)

 ここがすごくはまった。「どうやったらアニメーション止められるのかな」って考えてたけど、

〔ポイント〕

・切り替えるにしろ止めるにしろ「action.play()」処理だけ別の機能にする

・毎回オブジェクトを作り直すわけではない

・「play()」単体じゃなくて「.reset().fadeIn(1).play()」のように、はじめにresetを入れる&fadeInも入れれば滑らかに切り替わる

・「animate()」(フレームごとに再レンダリングする自作処理)は開始からずっと流し続ける。ここはボタン操作と関係ない

 これに気づいてから一気に進んだ。で、そのきっかけが実はThree.jsの公式サイトで、数えきれないくらいの実例とそのコードが丁寧に書かれているから、いいもの1つ見つけてそのコードを読み解いていくと「なるほど」ってなる。

 ちなみに、私が実際に参考にしたのは以下のサンプル。

よくよく見ると、右上に「Jump」「Yes」などのボタンが。押すとアニメーションが切り替わります。コードも右下の「< >」マークから見れる!

 そんなこんなで、実際に完成したコードがこちら。

完成コード

 3Dデータだけ用意してください。あとはhtmlファイルに丸ごと貼れば、ローカルサーバーなしでそのまま動きます!

 サーバー上で動かすなどの場合、webpackなどが必要になるかと思うので、「webpackって何?」という方は最下部リンクを参考にしてもらえれば幸いです。ではでは!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" href="static/images/favicon.ico">
    <style>
        body {
            margin: 0;
            padding: 0;
            width: 40rem;
            height: 40rem;
        
            background-color: #d5ffd5 ;
            background-size: cover;
            background-attachment: fixed;
            background-position: center center;
        }
        #buttonList  {
            position: absolute;
            width: 100%;
            bottom: 10px;
            padding: 0;
            display: flex;
            list-style: none;
            justify-content: center;
        }

        #buttonList  li {
            display: block;
            color: rgb(0, 0, 0);
            border: 1px rgb(0, 0, 0) solid;
            padding: 10px;
            text-align: center;
            min-width: 100px;
            margin: 0 10px;
            cursor: pointer;
        }
    </style>
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js"></script>
    <script src="https://unpkg.com/three@0.140.2/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.137.4/examples/js/controls/OrbitControls.js"></script>
    <script src="https://unpkg.com/three@0.137.4/examples/js/controls/TrackballControls.js"></script>
    <script src="https://unpkg.com/three@0.137.4/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
    <ul id="buttonList">
        <li data-action="CubeAction" data-loop="true">CubeAction</li>
        <li data-action="Rotate" data-loop="true">Rotate</li>
        <li data-action="Jump" data-loop="true">Jump</li>
        <li data-action="Fall" data-loop="true">Fall</li>
        <li data-action="Attack">Attack</li>
    </ul>
<script>

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,0.1,1000);
renderer.setClearColor(0xA3A3A3, 0);

const orbit = new THREE.OrbitControls(camera, renderer.domElement);
camera.position.set(10, 10, 10);
orbit.update();

let mixer, actions, activeAction, previousAction;

// 1、アニメーションクリップごとglbファイルを読み込む
const loader = new THREE.GLTFLoader();
loader.load('../static/models/anim.glb', (gltf) => {
    const model = gltf.scene; // 見た目(BG含む)
    const clips = gltf.animations; // アニメーション

    // 1、見た目
    scene.add(model);
    mixer = new THREE.AnimationMixer(model);

    // 2,アニメーション
    actions = {};
    clips.forEach(function(clip) {
        actions[clip.name] = mixer.clipAction(clip);
        console.log(clip.name);
        //actions[clip.name].play(); // ここonにすれば全アニメーションが動く
    });
    activeAction = actions[ 'CubeAction' ];
    //activeAction.setLoop(THREE.LoopOnce); //ループ設定(1回のみ)
    activeAction.clampWhenFinished = true; //アニメーションの最後のフレームでアニメーションが終了
    activeAction.reset().fadeIn(1).play();
    
}, undefined, function(error) {
    console.error(error);
});

function fadeToAction( name, duration ) {
    console.log(name,duration);
    previousAction = activeAction; //
    activeAction = actions[ name ];

    if ( previousAction !== activeAction ) {
        previousAction.fadeOut( duration );
    }
    activeAction.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn( duration ).play();
}

// 2、アニメーションを再生
const clock = new THREE.Clock();
function animate() {
    if(mixer) mixer.update(clock.getDelta());
    requestAnimationFrame(animate); //animate関数を実行(2回目以降)ちなみにこのanimateがコールバック関数、つまり無限ループ
    renderer.render(scene, camera);
// requestAnimationFrameは ブラウザの再描画の直前にコールバック関数(=animate)を呼び出し、次の再描画でアニメーションを更新する際に実行する
}
animate()


window.addEventListener('resize', function() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

var buttonList = document.getElementById('buttonList');
buttonList.addEventListener('click', onClickButtons);

function onClickButtons(event) {
    var target = event.target;
    fadeToAction(target.dataset.action,0.5); // htmlから読み込み
}
</script>
</body>
</html>

~webpack初めての方へ~

 おもに導入部分について触れてます。サーバー上で動かして、もしCORSポリシー関係のエラーが出てきた場合はぜひ参考にしてみてください。

 CDNは外部ファイルなので、まずローカルに外部ライブラリをダウンロード/インストールしてから、それらも丸ごと一つのjsファイルにまとめちゃおう、というイメージです。

 …といってもいざ使うとはじめは慣れないので、いろいろな情報集めながら根気強く頑張ってください…!


この記事が気に入ったらサポートをしてみませんか?