随着emlog的更新,我博客也如约而至。然后抱着学习的态度做了自己的第一个模板,但在参考别人博客的时候发现了一个有趣的小人动画插件。
然后我就想自己也做一个,以emlog插件的形式放到我的博客上。也顺便学习一下emlog的插件写法。
但是仅做这么有一个小人感觉没啥意思,索性来点有意思的。
我的初衷是养一只小狗小猫的,在博客溜达,会有一些类似睡觉,吃饭的动作,访客可以进行喂食,随着时间的推移和喂食量增加小猫也会长大。
巧的是,刚好在之前做AWD大屏动画的时候接触了three.js,前几天做森林防火演示动画的时候找模型时见到一直小恐龙。
开搞!
恐龙一共有5个动画:奔跑、吼叫、张望、进食、甩尾(击打)。这些动作足以满足我的需求。
一下是恐龙动画的js代码,时间仓促写的不是很别致,能看懂就看..
class Bee {
//定义类名,刚开始用的蜜蜂模型做的,感觉不好看改成恐龙后没做响应的更名
constructor(scene,Manager) {
//初始化传参scene,manager是three loader的管理(new THREE.LoadingManager();)
this.index=3;//当前第几个动作3是奔跑
this.setScope();
this.moveLevel={
run:5,
look:1,
bellow:1,
};
//let loader=new THREE.GLTFLoader(Manager);
let that=this;
that.path=[];
that.audio = new THREE.PositionalAudio(listener);
let audioLoader = new THREE.AudioLoader();
audioLoader.load('/content/plugins/Aman/js/11451.wav' , function (AudioBuffer) {
// 音频缓冲区对象关联到音频对象audio
that.audio.setBuffer(AudioBuffer);
that.audio.setRefDistance(20);
that.audio.setRolloffFactor(3);
that.audio.setMaxDistance(100);
that.audio.setVolume(0.1);
that.audio.setLoop(false); //是否循环
});
var loader = new THREE.FBXLoader(Manager);
loader.load("content/plugins/Aman/js/untitled.fbx", function (object ) { //fbx模型
object.scale.multiplyScalar(0.015);
that.animations=object.animations;
object.traverse( function ( child ) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
that.animations=object.animations;
that.scene=object;
that.mixer=new THREE.AnimationMixer(object.children[0]);
that.actions=[];
object.position.z=window.innerWidth*0.1;
for ( let i = 0; i <that.animations.length; i ++ ) {
const clip = object.animations[i];
const action = that.mixer.clipAction( clip );
action.clampWhenFinished=true;
that.actions.push(action) ;
}
//动作结束回调,在行走/奔跑一段时间后,会切换其他动作,该动作执行完毕后继续奔跑
that.mixer.addEventListener( 'finished', function( e ) {
that.run();
that.setPath();
});
object.lookAt(new THREE.Vector3(0,0,0));
scene.add(object);
});
}
//奔跑动作,time的大小控制动画播放的速度,delay为切换动画淡入淡出时间,感觉没啥用(其实是我没搞懂)
run(time=9,delay=1){
this.fadeOutLast(delay);
this.index=3;
this.actions[3].reset().setDuration(time).play();
}
//进食
eat(time=10,delay=1){
this.fadeOutLast(delay);
this.index=4;
this.actions[4].reset().setDuration(time).setLoop(THREE.LoopOnce).play().fadeIn(delay);
}
//吼叫
bellow(time=10,delay=1){
this.fadeOutLast(delay);
this.index=2;
this.actions[2].reset().setDuration(time).setLoop(THREE.LoopOnce).play().fadeIn(delay);
let that=this
setTimeout(function () {
//吼叫时播放音效 that.audio.getOutput().setPosition(that.scene.position.x,that.scene.position.y, that.scene.position.z);
that.audio.play();
},3300)
}
//播放吼叫音效
playSound(){
this.audio.getOutput().setPosition(this.scene.position.x, this.scene.position.y, this.scene.position.z);
this.audio.play();
}
//甩尾击打动作
hit(time=10,delay=1){
this.fadeOutLast(delay);
this.index=1;
this.actions[1].reset().setDuration(time).setLoop(THREE.LoopOnce).play().fadeIn(delay);
}
//张望动作
look(time=10,delay=1,loop=1){
this.fadeOutLast(delay);
this.index=0;
this.actions[0].reset().setDuration(time).setLoop(THREE.LoopOnce).play().fadeIn(delay);
}
//一个菜单,恐龙会从当前位置跑到屏幕前,很逼真哦
goOut(){
this.stopAll();
this.run(5)
let path=[];
let len=this.path.length;
if(len>0){
path[0]=this.path[0];
}else{
path[0]=new THREE.Vector3(this.scene.position.x, this.scene.position.y,this.scene.position.z);
}
path[1]=new THREE.Vector3( 0,0,0);
path[2]=new THREE.Vector3( camera.position.x-25, 0, camera.position.z);
var curve = new THREE.CatmullRomCurve3(path, false, 'centripetal', 5);
this.path=curve.getPoints(curve.getLength()*0.5);
}
update(delta){
this.mixer.update(delta)
}
stopAll(index=-1){
for( let i = 0; i < this.actions.length; i ++){
if(index!=i){
this.actions[i].stop();
}
}
}
fadeOutLast(time=1){
if(this.index==3){
this.actions[3].stop()
}else if(this.index>-1){
this.actions[this.index].stop().fadeOut(time);
}
}
//设置随机行走路径
setPath(){
if(this.path.length>2000){
return
}
let path=[];
let len=this.path.length;
if(len>0){
path[0]=this.path[len-1];
}else{
path[0]=new THREE.Vector3(this.scene.position.x, this.scene.position.y,this.scene.position.z);
}
console.log(path)
for(let i=1;i<5;i++){
path[i]=this.getRandomPoint();
}
var curve = new THREE.CatmullRomCurve3(path, false, 'centripetal', 10);
this.path.push(...curve.getPoints(curve.getLength()*8));
}
//移动 three animation 更新该操作,
move(){
if(this.index!=3){
return ;
}
if(this.path.length>0){
this.scene.position.set(this.path[0].x,this.path[0].y,this.path[0].z);
this.path.splice(0,1);
if(this.path.length>0){
this.scene.lookAt(new THREE.Vector3( this.path[0].x, this.path[0].y,this.path[0].z));
}
}else{
let rand=this.randomNum(0,4);
switch (rand) {
case 0 :
this.bellow();
break;
case 1:
this.eat();
break;
case 2:
this.look();
break;
case 3:
this.hit();
break;
case 4:
this.goOut;
break;
}
}
}
setScope(){
let z=window.innerWidth*0.1;
this.scope= {
x: [-50, 50],
y:[0,0],
z:[-(z),z]
};
}y
getRandomPoint(){
let x=this.randomNum(this.scope.x[0],this.scope.x[1]);
let y=this.randomNum(this.scope.y[0],this.scope.y[1]);
let z=this.randomNum(this.scope.z[0],this.scope.z[1]);
return new THREE.Vector3(x,y,z);
}
randomNum(minNum,maxNum){
switch(arguments.length){
case 1:
return parseInt(Math.random()*minNum+1,10);
break;
case 2:
return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10);
break;
default:
return 0;
break;
}
}
//开始行走动画
start(){
this.run();
this.setPath();
}
//执行随机动作
randomAction(){
if(this.index==3){
let rand=this.randomNum(0,4);
switch (rand) {
case 0 :
this.bellow();
break;
case 1:
this.eat();
break;
case 2:
this.look();
break;
case 3:
this.hit();
break;
case 4:
this.goOut();
break;
}
}
}
}
然后是three的场景相机等代码
let wHeight = window.innerHeight;
let Bees = [], Count = 1, scene, camera, renderer, controls, clock;
let listener,objects=[];
let Manager = new THREE.LoadingManager();
//创建相机
clock = new THREE.Clock();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / wHeight, 0.2, 1000);
listener = new THREE.AudioListener();
camera.add(listener);
camera.position.set(400, 0, 0);
camera.lookAt(0, 145.2, 0)
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
//设置周围环境和场景
function initScene() {
scene = new THREE.Scene();
}
//载入恐龙(开始做的蜜蜂,改成恐龙后没改名字)
function initBees() {
for (let i = 0; i < Count; i++) {
let b = new Bee(scene, Manager);
Bees.push(b);
}
}
//设置光源
function initLight() {
/*scene.add( new THREE.AmbientLight( 0x404040 ) );
pointLight = new THREE.PointLight( 0xffffff, 1 );
pointLight.position.copy( camera.position );
scene.add( pointLight );*/
let hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
let dirLight = new THREE.DirectionalLight(0xefefef,0.4);
dirLight.position.set(100, 50, 0);
dirLight.castShadow = true;
dirLight.shadowCameraVisible = true;
scene.add(dirLight);
}
//初始化渲染
function initRender() {
//渲染
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true,});
renderer.setSize(window.innerWidth, wHeight);
renderer.setClearAlpha(0.01);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
}
//动作
function animate() {
var delta = clock.getDelta();
//controls.update();
renderer.render(scene, camera);
for (let i = 0; i < Bees.length; i++) {
Bees[i].update(delta);
Bees[i].move();
}
//requestAnimationFrame(animate);
}
initScene();
initLight();
initBees();
//loader载入回调
Manager.onStart = function (url, itemsLoaded, itemsTotal) {
//console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );
};
Manager.onLoad = function () {
console.log('Loading complete!');
initRender();
setInterval("animate()", 15);
};
Manager.onProgress = function (url, itemsLoaded, itemsTotal) {
//console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' );
};
Manager.onError = function (url) {
console.log('There was an error loading ' + url);
};
//监听窗口大小变动使canves做响应变动
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
if(renderer){
wHeight = window.innerHeight;
camera.aspect = window.innerWidth / wHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, wHeight);
for (let i = 0; i < Bees.length; i++) {
Bees[i].setScope();
}
}
}
//监听鼠标,未完成 。预计右键菜单,可以对恐龙进行喂食、查看信息和互动操作
document.addEventListener("mousedown", (event) => {
event.preventDefault();
let intersect = []
mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - ((event.clientY+2) /renderer.domElement.clientHeight) * 2 +1;
objects=[];
raycaster.setFromCamera(mouse, camera);
for (let i in scene.children) {
if (scene.children[i] instanceof THREE.Group || scene.children[i] instanceof THREE.Scene) {
let rayArr = raycaster.intersectObjects(scene.children[i].children, true)
objects.push(...rayArr)
} else if (scene.children[i] instanceof THREE.Mesh) {
objects.push(...raycaster.intersectObject(scene.children[i]))
}
}
console.log(objects,Bees[0].index)
if(objects.length>0){
Bees[0].scene.lookAt(1000, 0,0);
Bees[0].randomAction();
}
}, false)
代码总共就以上那么多,很简单的一个东西。
目前只完成了心目中的60%。
还有不少等以后有时间再继续更新,至于资源什么的就从我博客扒吧。
然后分享一个接触three.js自己研究出来的一个压缩体积技巧:
开始恐龙的格式是glb,纹理和模型都封装在一起的。但是整体有28MB,加载速度太慢了。然后我用blender改成FBX格式,贴图也导出来了,然后对贴图进行压缩处理,两张图从15MB压缩到400+KB,加上模型也就4MB左右,比之前少了24MB(模型其实还可以再优化)
演示视频:传送门
评论
啊啊 2021-07-30 10:33
博主 这个有视频教程吗 没看懂....
Aman 2021-07-31 01:09
@啊啊:没有,等有空我完善下打包成插件