-
在博客养一头小恐龙
随着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(模型其实还可以再优化) 演示视频:传送门
最新评论
Aman 2年前
@阿巴阿巴:define
阿巴阿巴 3年前
如果waf屏蔽了$该怎么绕啊楼主
Aman 3年前
@啊啊:没有,等有空我完善下打包成插件
啊啊 3年前
博主 这个有视频教程吗 没看懂....
Aman 3年前
@波波:密码随意
波波 3年前
这句话的怎么连接啊,楼主
Aman 3年前
Hi