【アプリ開発日記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、アニメーション切り替えの処理を書く(メイン)
ここがすごくはまった。「どうやったらアニメーション止められるのかな」って考えてたけど、
これに気づいてから一気に進んだ。で、そのきっかけが実はThree.jsの公式サイトで、数えきれないくらいの実例とそのコードが丁寧に書かれているから、いいもの1つ見つけてそのコードを読み解いていくと「なるほど」ってなる。
ちなみに、私が実際に参考にしたのは以下のサンプル。
そんなこんなで、実際に完成したコードがこちら。
完成コード
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ファイルにまとめちゃおう、というイメージです。
…といってもいざ使うとはじめは慣れないので、いろいろな情報集めながら根気強く頑張ってください…!
この記事が気に入ったらサポートをしてみませんか?