在博客养一头小恐龙

Aman
评论:2 阅读:6220
时间:2021-6-10 02:12:15 分类: 优秀文章

随着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(模型其实还可以再优化)
演示视频:传送门

标签: Three.js 3D 插件

评论
啊啊 2021-07-30 10:33

博主  这个有视频教程吗 没看懂....

回复
Aman 2021-07-31 01:09

@啊啊:没有,等有空我完善下打包成插件

回复
Powered by emlog pro © 主题Aman 豫ICP备16010520号-1