源码地址-codepen Threejs框架,使用JS控制背景变换,镜头移动,控制模型变换等。
预备知识
// 获取gltf 对象实例! - 可修改 corps = sheepGroup.getObjectByName(‘corps’); head = sheepGroup.getObjectByName(‘Head’);
功能实现
鼠标移动,头部跟着移动
眨眼动画效果实现
点击跳跃-实现鼠标、手机、按键的实现
跳跃之前的动画实现 - 下蹲,脸部变色
跳跃中的动画实现 - 跃起角度变换
跳跃不同高度,显示不同的背景 - 背景变换
超出跳跃的高度,触发拉便便效果,同时改变镜头。。。- 计数实现
代码细节
加载资源
three.js - 核心库
GLTFLoader.js - 加载模型
BufferGeometryUtils - 模型合并操作
gsap - 背景变换1 2 3 4 https://unpkg.com/three@0.128.0/build/three.js https://unpkg.com/three@0.128.0/examples/js/loaders/GLTFLoader.js https://unpkg.com/three@0.128.0/examples/js/utils/BufferGeometryUtils.js https://unpkg.co/gsap@3/dist/gsap.min.js
定义变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 //THREEJS Variables let renderer, camera, scene,// 基础场景搭建scene,camera,renderer floor, cameraTarget = new THREE.Vector3(), // 控制镜头!!! shadowLight, // light keyLight, hemiLight, sheepGroup, // 各个模型的组合 - 统一渲染 - 方便控制(foreach) spaceGroup, cloudsGroup, treeGroup, head,// 模型内部细节 poo, corps, eyeLeft, eyeRight, pupilleLeft, pupilleRight; let isDown = false, // 标记变量 downCounter = 0, // 计数 - 放入循环中 pooCounter = 0, status = 'default', // 通过状态控制 - 字符串! mouse = { x: 0, y: 0 }, currentJumpStemp = 0; // 跳多高???? // Whole config const CONFIG = { CAMERA: { // CAMERA参数调节 fov: 45, nearPlane: 1, farPlane: 20, }, PHYSICS: { // 物体数据调节 - 用于比较 - 累加 gravity: 0.1, falling: false, colorize: false, force: 0, forceTarget: 0, }, BLINK: { // 展演动画 - 主要是时间参数。 delay: THREE.MathUtils.randInt(100, 300), counter: 0, length: 7, double: false, }, STEPS: [{ // 跳跃高度 - 角度控制,颜色变化(给背景的?? - yes - gasp控制) altitude: 2, impulse: 0.2, angle: 10, diffStart: 0, flyingLerp: 0.05, colorStart: '#cfe1e3', colorEnd: '#cfe1e3', }, { altitude: 6, impulse: 0.15, angle: 22, diffStart: 0, flyingLerp: 0.05, colorStart: '#51c7ab', colorEnd: '#51b1c7', }, { altitude: 12, impulse: 0.1, angle: 30, diffStart: 1.5, flyingLerp: 0.03, colorStart: '#6eaceb', colorEnd: '#4f9be8', }, { altitude: 30, impulse: 0.05, angle: 40, diffStart: 3, flyingLerp: 0.015, colorStart: '#0f0d1c', colorEnd: '#000000', }], COLORS: { // 整体颜色变量 - 云,地面,树,树叶,便便,变脸 clouds: 0xfefae0, floor: 0x4b6b30, tree: 0x1a1412, leaf: 0x133019, poo: 0x402e2a, white: new THREE.Color(1, 1, 1), red: new THREE.Color(0xf71735), } }
init函数
init()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 设置scene, camera, renderer 设置全局事件 窗口变换 鼠标按下 - 触摸开始 - onDown 鼠标抬起 - 触摸结束 - onUp 鼠标移动 - 触摸移动 - onMove - onTouchMove 键盘按下 - onKeyDown 键盘抬起 - onKeyUp window.addEventListener("resize", onResize); window.addEventListener("mousedown", onDown); window.addEventListener("touchstart", onDown); window.addEventListener("mouseup", onUp); window.addEventListener("touchend", onUp); window.addEventListener("mousemove", onMove); window.addEventListener("touchmove", onTouchMove); window.addEventListener("keydown", onKeyDown); window.addEventListener("keyup", onKeyUp);
添加模型
addFloor()
添加一个Mesh,修改x轴,修改z位置,定义接收阴影,设置水平放大效果 添加进场景
1 2 3 4 5 6 7 8 floor = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: CONFIG.COLORS.floor })); floor.rotation.x = -Math.PI / 2; floor.position.z = 1; floor.receiveShadow = true; floor.scale.set(20, 10, 1); scene.add(floor);
addSheep()
Blender中查看模型细节 - 可用Js绑定该位置 - 然后调整参数!
通过GLTFLoader加载模型 - 异步 设置位置,缩放,接收阴影,投掷阴影效果,旋转Y轴 - 添加进sheepGroup - 添加进场景
定义便便模型 - 一个正方形 - 褐色。 定义名字,位置(隐藏在地板下),写入userData数据 (保存模型数据 ) - 添加进sheepGroup
定义数据集 - 来自 小羊模型 ,遍历模型,写入userData数据 ,接收阴影
把 小羊模型 每个部分,绑定全局变量 - 用于修改参数!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 const loader = new THREE.GLTFLoader(); loader.load( 'https://brand.kffein.com/codepen/jumping-sheep/sheep.gltf', (gltf) => { sheepGroup = gltf.scene; sheepGroup.position.set(0, 0, 0.8); sheepGroup.scale.set(0.5, 0.5, 0.5) sheepGroup.receiveShadow = true; sheepGroup.castShadow = true; sheepGroup.rotation.y = THREE.MathUtils.degToRad(-50); // 添加! scene.add(sheepGroup); // 粑粑 poo = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.25, 0.25), new THREE.MeshLambertMaterial({ fog: false, color: CONFIG.COLORS.poo })); poo.name = "poo"; poo.position.set(0, -1.2, 0) poo.userData.position = poo.position.clone(); sheepGroup.add(poo); const exceptions = ['corps', 'Head', 'Nose', 'Face', 'EyeLeft', 'EyeRight']; sheepGroup.traverse(child => { if (child.isMesh) { child.material.fog = false; // userData - 写入做什么? child.userData.position = child.position.clone(); child.userData.scale = child.scale.clone(); child.castShadow = true; if (exceptions.indexOf(child.name) === -1) child.receiveShadow = true; } }); // 获取gltf 对象实例! - 可修改 corps = sheepGroup.getObjectByName('corps'); head = sheepGroup.getObjectByName('Head'); eyeLeft = head.getObjectByName("EyeLeft"); eyeRight = head.getObjectByName("EyeRight"); pupilleLeft = head.getObjectByName("PupilleLeft"); pupilleRight = head.getObjectByName("PupilleRight"); pateBottomLeft = sheepGroup.getObjectByName("pateBottomLeft"); pateBottomRight = sheepGroup.getObjectByName("pateBottomRight"); pateFrontLeft = sheepGroup.getObjectByName("pateFrontLeft"); pateFrontRight = sheepGroup.getObjectByName("pateFrontRight"); }, );
addDecor()
添加树模型 - CylinderGeometry + SphereGeometry + clone() 添加云模型 - BoxGeometry * 3 + mergeBufferGeometries 添加星星 - BufferGeometry * 1000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 //------ TREES // 统一渲染,节约性能 treeGroup = new THREE.Group(); treeGroup.position.set(3, 1, 0); scene.add(treeGroup); const treeGeom = new THREE.CylinderGeometry(.2, .2, 6, 3); const tree = new THREE.Mesh(treeGeom, new THREE.MeshToonMaterial({ fog: false, color: CONFIG.COLORS.tree })); tree.castShadow = true; tree.position.x = -0.15; tree.position.z = 1.2; tree.rotation.y = Math.PI / 2; treeGroup.add(tree); const leafGeom = new THREE.SphereGeometry(2, 4, 4); const leaf = new THREE.Mesh(leafGeom, new THREE.MeshToonMaterial({ fog: false, color: CONFIG.COLORS.leaf })); leaf.position.set(0, 3, 0); leaf.scale.set(0.75, 0.75, 0.75); tree.add(leaf); // 另一棵树 const treeLeft = tree.clone(); treeLeft.rotation.y = -Math.PI / 2; treeLeft.position.set(-6, -1.25, -0.5) treeGroup.add(treeLeft); //---- CLOUDS cloudsGroup = new THREE.Group(); cloudsGroup.position.set(0, 8, 0); scene.add(cloudsGroup); const cloudPart1 = new THREE.BoxGeometry(1.5, 1.5, 1.5) const cloudPart2 = new THREE.BoxGeometry(1.5, 1.5, 1.5) const cloudPart3 = new THREE.BoxGeometry(1.5, 1.5, 1.5) cloudPart1.translate(-0.5, 0, 0); cloudPart2.translate(0, 0.5, 0); cloudPart3.translate(1, -0.5, 0); // 合并Geometry const cloudGeom = THREE.BufferGeometryUtils.mergeBufferGeometries([cloudPart1, cloudPart2, cloudPart3]); const cloud = new THREE.Mesh(cloudGeom, new THREE.MeshLambertMaterial({ fog: false, color: CONFIG.COLORS.clouds }, 500)); cloud.scale.set(1, 0.7, 0.8); cloud.rotation.y = -Math.PI / 8; cloud.position.set(6, 4, -10); cloudsGroup.add(cloud); const cloudLeft = cloud.clone(); cloudLeft.scale.set(1.2, 0.8, 0.5); cloudLeft.position.set(-8, 1, -7); cloudLeft.rotation.y = -Math.PI / 12; cloudsGroup.add(cloudLeft); //---- SPACE spaceGroup = new THREE.Group(); spaceGroup.position.set(0, 30, 0); const vertices = []; for (let i = 0; i < 1000; i++) { // 随机函数!!! const x = THREE.MathUtils.randFloatSpread(10); const y = THREE.MathUtils.randFloatSpread(10); const z = THREE.MathUtils.randFloatSpread(10); vertices.push(x, y, z); } const spaceGeom = new THREE.BufferGeometry(); spaceGeom.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); const spaceMat = new THREE.PointsMaterial({ size: 0.02, fog: false, transparent: true, color: 0xFFFFFF }); const points = new THREE.Points(spaceGeom, spaceMat); spaceGroup.add(points) scene.add(spaceGroup);
addLights()
添加DirectionalLight * 2 + HemisphereLight
1 2 3 4 5 6 7 8 9 10 11 shadowLight = new THREE.DirectionalLight(0xffffff, 1); shadowLight.position.set(0.5, 5, 2.5); shadowLight.castShadow = true; scene.add(shadowLight); keyLight = new THREE.DirectionalLight(0xffffff, 1); keyLight.position.set(3, 0, 1); scene.add(keyLight); hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.5); scene.add(hemiLight);
run()
每次循环要做的事情! - 重点优化所在
if (!sheepGroup) return; - 如果没有加载出来,不执行。
if (isDown) - 按下 - 计数
if (!CONFIG.PHYSICS.force && status === ‘default’) -移动头部动画 + 跳跃之前动画 else if (status === ‘poo’) - 拉便便动画
updateFlying(); // falling + flying
//update elements position - camera, sheepGroup, shadowLight, keyLight
//camera lookAt - cameraTarget(sheepGroup.position)
//render -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 render(); requestAnimationFrame(run); function render() { if (!sheepGroup) return; //increment the down counter if (isDown) { downCounter += 1; // console.log(downCounter) } if (!CONFIG.PHYSICS.force && status === 'default') { // move head sheep updateHead(); // do anim before jumping updateFlexion(); } else if (status === 'poo') { // 如何进入这里 - status makeAPoo(); } updateFlying(); //update elements position camera.position.y = 1 + CONFIG.PHYSICS.force + (CONFIG.PHYSICS.force * 0.1); sheepGroup.position.y = 0.65 + CONFIG.PHYSICS.force; shadowLight.position.y = 5 + CONFIG.PHYSICS.force; keyLight.position.y = CONFIG.PHYSICS.force; // camera lookAt cameraTarget.set(sheepGroup.position.x, sheepGroup.position.y, sheepGroup.position.z); camera.lookAt(cameraTarget); //render renderer.render(scene, camera); }
功能函数
jump()
currentJumStep - 取 按下计数 / 25, CONFIG.STEPS.length - 1 的最小值,
sheepGroup.rotation.z - THREE.MathUtils.degToRad(CONFIG.STEPS[currentJumpStemp].angle) - 角度
CONFIG.PHYSICS.forceTarget - 取CONFIG.STEPS[currentJumpStemp].altitude - 高度
downCounter - 0 pooCounter - 0 status - “jump” - 状态
updateHead() - 重置位置 - 里面有跳跃和非跳跃 两种变换条件
// 什么时候调用jump() ???? onUp() - 抬起事件!!!
1 2 3 4 5 6 7 8 9 10 11 12 // 140/25 - 5.6 和 3 比较, 这里不是超时停止的位置。 currentJumpStemp = Math.min(Math.round(downCounter / 25), CONFIG.STEPS.length - 1); // console.log(currentJumpStemp, Math.round(downCounter / 25), CONFIG.STEPS.length - 1) sheepGroup.rotation.z = THREE.MathUtils.degToRad(CONFIG.STEPS[currentJumpStemp].angle); CONFIG.PHYSICS.forceTarget = CONFIG.STEPS[currentJumpStemp].altitude; downCounter = 0; pooCounter = 0; status = 'jump'; //reset the position because we are already jumping updateHead();
updateHead()
if (eyeLeft.userData.beforeJump && eyeRight.userData.beforeJump) - 设置为false - 开关?
resetEyes() - 设置为false
updateHead() - else 中设置为true
if (downCounter === 0) - 没有起跳的时候
if (CONFIG.BLINK.counter > CONFIG.BLINK.delay && CONFIG.BLINK.counter <= CONFIG.BLINK.delay + CONFIG.BLINK.length) // 没超过就可以执行眨眼动画!
else if (CONFIG.BLINK.counter > CONFIG.BLINK.delay + CONFIG.BLINK.length) // 超了就重置!重置左右眼睛的y轴 + counter + delay(随机来一个数值!)
else - 起跳了!
head角度变换 - lerp
concentration - 变形幅度 - Math.min(1, downCounter / 100);
eyeLeft + eyeRight 变形(眼球) - scale
pupilleLeft + pupilleRight 变形(眼珠)- scale
CONFIG.BLINK.counter = 0;
colorLerp - 变色幅度 - Math.min(1, downCounter / 50);
pooBurnOut - colorLerp === 1; - 拉便便之前的状态控制。。。
head.material.color.lerpColors - 变色秘法
if (pooBurnOut) - 修改头部位置 + pooCounter 累加!
if (pooCounter >= 90) - 修改状态
// 什么时候调用updateHead() ???
jump()中(用于停止movehaed)
render中 if判断(用于movehead)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 // true or false if (eyeLeft.userData.beforeJump && eyeRight.userData.beforeJump) { resetEyes(); } //--- follow mouse // 这里的数据不好理解。。。趋近 // 这缩进看的明白 - 没有计数时! if (downCounter === 0) { head.rotation.y = THREE.MathUtils.lerp(head.rotation.y, mouse.x - THREE.MathUtils.degToRad(55), 0.15); head.rotation.z = THREE.MathUtils.lerp(head.rotation.z, mouse.y - THREE.MathUtils.degToRad(5), 0.1); head.material.color.lerpColors(CONFIG.COLORS.white, CONFIG.COLORS.red, 0); //----- blink if (CONFIG.BLINK.counter > CONFIG.BLINK.delay && CONFIG.BLINK.counter <= CONFIG.BLINK.delay + CONFIG.BLINK .length) { // 时间 + y变化! eyeLeft.scale.y = Math.min(0, eyeLeft.scale.y + 0.06); eyeRight.scale.y = Math.min(0, eyeRight.scale.y + 0.06); } else if (CONFIG.BLINK.counter > CONFIG.BLINK.delay + CONFIG.BLINK.length) { eyeLeft.scale.y = eyeLeft.userData.scale.y; eyeRight.scale.y = eyeRight.userData.scale.y; CONFIG.BLINK.counter = 0; CONFIG.BLINK.delay = THREE.MathUtils.randInt(100, 300); } CONFIG.BLINK.counter += 1; } else { // 跳跃计数中! head.rotation.y = THREE.MathUtils.lerp(head.rotation.y, 0.74 - THREE.MathUtils.degToRad(55), 0.25); head.rotation.z = THREE.MathUtils.lerp(head.rotation.z, 0.55 - THREE.MathUtils.degToRad(5), 0.2); const concentration = Math.min(1, downCounter / 100); eyeLeft.userData.beforeJump = true; eyeRight.userData.beforeJump = true; eyeLeft.scale.y = Math.min(-0.15, eyeLeft.userData.scale.y + (concentration * 0.8)); eyeRight.scale.y = Math.min(-0.15, eyeRight.userData.scale.y + (concentration * 0.8)); pupilleLeft.scale.y = Math.min(0.3, pupilleLeft.userData.scale.y + (concentration * 0.8)); pupilleRight.scale.y = Math.min(0.3, pupilleLeft.userData.scale.y + (concentration * 0.8)); CONFIG.BLINK.counter = 0; const colorLerp = Math.min(1, downCounter / 50);// 取最小值,按下超过50???嗯! const pooBurnOut = colorLerp === 1; head.material.color.lerpColors(CONFIG.COLORS.white, CONFIG.COLORS.red, colorLerp); //shaking - warning if (pooBurnOut) { // 头部变化 head.position.x = head.userData.position.x + (Math.cos(downCounter) * 0.01); head.position.y = head.userData.position.y - (Math.sin(downCounter) * 0.01); // console.log('pooBurnOut=',pooBurnOut, 'pooCounter=',pooCounter, 'downCounter=',downCounter) // 累加? pooCounter += 1; if (pooCounter >= 90) { status = 'poo'; // 这里控制了超过的状态 // 140 = 50 + 90!!!完全了解!!太赞了,状态控制,若有所思。。。 // console.log('pooBurnOut=',pooBurnOut, 'pooCounter=',pooCounter, 'downCounter=',downCounter) console.log(pooCounter) // 只有90 } } }
updateFlexion()
把绑定好的数据,一起放入数组,统一处理,,!!! if (downCounter > 0) - 按下 - 整体下降 else - 抬起 - 还原位置
什么时候用updateFlexion()???
makeAPoo() - if (pooCounter <= 200) 中
updateFlying() - else if (CONFIG.PHYSICS.forceTarget) 中
render() - if (!CONFIG.PHYSICS.force && status === ‘default’) 中
这里未解。。。。。。。。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 把绑定好的数据,一起放入数组,统一处理,,!!! const feets = [pateBottomLeft, pateBottomRight, pateFrontLeft, pateFrontRight] if (downCounter > 0) { // 按下状态, feets.forEach((child) => { // 下降! child.position.y = Math.max(-0.95, child.position.y - 0.05); }); // 这是啥? - 身体?? - yes corps.position.y = Math.max(-0.15, corps.position.y - 0.035); } else { feets.forEach((child) => { child.position.y = Math.min(child.userData.position.y, child.position.y + 0.05); }); corps.position.y = Math.min(0, corps.position.y + 0.035); }
makeAPoo()
if (pooCounter <= 200)
重置按下计数
重置眼睛动画
更新下蹲动画
改变眼珠朝向 - 盯着便便
重置头部角度和颜色
改变摄像头的位置,地板位置,便便位置 - lerp else
改变摄像头的位置,地板位置,便便位置 - lerp - 复原
if (pooCounter > 300) - 重置!status = ‘default’
pooCounter += 1 - 累加
什么时候用?- render() - else if (status === ‘poo’) {
这里重点,重置动画,更新位置!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 if (pooCounter <= 200) { downCounter = 0; resetEyes(); updateFlexion(); // look at the poo pupilleLeft.position.z = pupilleLeft.userData.position.z - 0.5; pupilleRight.position.z = pupilleRight.userData.position.z - 0.5; //reset head and color head.rotation.y = THREE.MathUtils.lerp(head.rotation.y, 0.15 - THREE.MathUtils.degToRad(55), 0.25); head.rotation.z = THREE.MathUtils.lerp(head.rotation.z, 0.08 - THREE.MathUtils.degToRad(5), 0.2); head.material.color.lerpColors(CONFIG.COLORS.white, CONFIG.COLORS.red, 0); //camera focus on the poo camera.position.x = THREE.MathUtils.lerp(camera.position.x, -2, 0.1); floor.rotation.z = THREE.MathUtils.lerp(floor.rotation.z, THREE.MathUtils.degToRad(-28), 0.1); poo.posiftion.x = THREE.MathUtils.lerp(poo.position.x, -2, 0.4); poo.position.y = Math.max(-1.15, poo.position.y - CONFIG.PHYSICS.gravity); poo.rotation.x = THREE.MathUtils.lerp(poo.rotation.x, Math.PI / 2, 0.12); } else { //camera focus on the poo camera.position.x = THREE.MathUtils.lerp(camera.position.x, 0, 0.1); floor.rotation.z = THREE.MathUtils.lerp(floor.rotation.z, 0, 0.1); poo.position.y = Math.max(-2, poo.position.y - 0.01); // reset pupilleLeft.position.z = pupilleLeft.userData.position.z; pupilleRight.position.z = pupilleRight.userData.position.z; if (pooCounter > 300) { status = 'default' pooCounter = 0; poo.rotation.x = 0; poo.position.set(poo.userData.position.x, poo.userData.position.y, poo.userData.position.z); } } pooCounter += 1
updateFlying()
diff - Math.abs(CONFIG.PHYSICS.force - CONFIG.PHYSICS.forceTarget);
force: 0, forceTarget: 0, 初始值
forceTarget - jump()中修改 - 高度altitude
force - render()中调整camera, sheepGroup, shadowLight, keyLight
force - else if (CONFIG.PHYSICS.forceTarget)中修改 - 用于趋向 forceTarget!
currentStep - 从0开始 - 用于改变背景!
updateBackgroundAt(currentStep, 1); - 改变背景!
if (CONFIG.PHYSICS.falling) - 判断是否是下落状态 - 上升 进入状态
if (!CONFIG.PHYSICS.colorize) - 开关 ? 作用?
if (!CONFIG.PHYSICS.force) - 如果没有force,就重置(下降) + colorize设置false
else if (CONFIG.PHYSICS.forceTarget) - 如果起跳了!
if (!CONFIG.STEPS[currentJumpStemp].diffStart || diff < CONFIG.STEPS[currentJumpStemp].diffStart)
// 初始值 + 边界条件 - 改变sheepGroup.rotation.z + CONFIG.PHYSICS.force
if (CONFIG.PHYSICS.force)
// 跳跃中 CONFIG.PHYSICS.falling = diff < 0.01;
else - 下降! - sheepGroup.rotation.z
什么时候调用?? - render()中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 // ---Falling const diff = Math.abs(CONFIG.PHYSICS.force - CONFIG.PHYSICS.forceTarget); let currentStep = 0; CONFIG.STEPS.forEach((step, index) => { if (CONFIG.PHYSICS.force > step.altitude * .5) { currentStep = index; } }); updateBackgroundAt(currentStep, 1); // true or false if (CONFIG.PHYSICS.falling) { CONFIG.PHYSICS.force = Math.max(CONFIG.PHYSICS.force - CONFIG.PHYSICS.gravity, 0); CONFIG.PHYSICS.gravity += 0.0075; sheepGroup.rotation.z = THREE.MathUtils.lerp(sheepGroup.rotation.z, THREE.MathUtils.degToRad(-CONFIG.STEPS[ currentJumpStemp].angle * .8), 0.2); if (!CONFIG.PHYSICS.colorize) { CONFIG.PHYSICS.colorize = true; } if (!CONFIG.PHYSICS.force) { reset(); CONFIG.PHYSICS.colorize = false; } } // ---Flying else if (CONFIG.PHYSICS.forceTarget) { if (!CONFIG.STEPS[currentJumpStemp].diffStart || diff < CONFIG.STEPS[currentJumpStemp].diffStart) { sheepGroup.rotation.z = THREE.MathUtils.lerp(sheepGroup.rotation.z, 0, CONFIG.STEPS[currentJumpStemp] .flyingLerp); } CONFIG.PHYSICS.force = THREE.MathUtils.lerp(CONFIG.PHYSICS.force, CONFIG.PHYSICS.forceTarget, CONFIG.STEPS[ currentJumpStemp].impulse); if (CONFIG.PHYSICS.force) CONFIG.PHYSICS.falling = diff < 0.01; updateFlexion(); } else { sheepGroup.rotation.z = THREE.MathUtils.lerp(sheepGroup.rotation.z, 0, 0.4); }
updateBackgroundAt(index, speed)
step - 定位到数据集 bg - 找到背景的div gasp.to - 设置背景颜色变换 - linear-gradient
1 2 3 4 5 const step = CONFIG.STEPS[index]; const bg = document.querySelector(".scene-background"); gsap.to(bg, speed, { backgroundImage: `linear-gradient(0deg, ${step.colorStart} 0%, ${step.colorEnd} 100%)` });
reset()
改变数据 - forceTarget,falling,gravity 改变状态 - ‘default’
1 2 3 4 CONFIG.PHYSICS.forceTarget = 0; CONFIG.PHYSICS.falling = false; CONFIG.PHYSICS.gravity = 0.1; status = 'default';
resetEyes()
从userData中恢复数据! 改变眼珠,眼球的大小 - scale
1 2 3 4 5 6 7 pupilleLeft.scale.y = pupilleLeft.userData.scale.y; pupilleRight.scale.y = pupilleRight.userData.scale.y; eyeLeft.scale.y = eyeLeft.userData.scale.y; eyeLeft.userData.beforeJump = false; eyeRight.scale.y = eyeRight.userData.scale.y; eyeRight.userData.beforeJump = false;
事件函数
onResize()
renderer, camera 根据窗口大小,重新渲染
1 2 3 renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix();
onDown()
设置状态 为 “jump” - 是否跳跃的依据!(跳跃前,跳跃中,poo) isDown = true - 开始计数
在哪里判断??? 主要给render用,每次都能考虑到它!!!
1 2 if (status === 'jump') return; isDown = true;
onKeyDonw(e)
判断按下的是否是空格,是调用onDown
1 2 3 if (e.keyCode === 32) { this.onDown(); }
onUp()
1 2 isDown = false; if (status === 'default') jump();
onKeyUp(e)
1 2 3 4 // 32 Space(空格键) if (e.keyCode === 32) { this.onUp(); }
onMove(e)
修改鼠标的位置
在哪里使用?? updateHead()中,修改head的旋转角度!
1 2 mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
onTouchMove(e)
和触摸的坐标位置不一样! 还是调用 onMove,传入当前参数!
1 2 3 4 this.onMove({ clientX: e.touches[0].clientX, clientY: e.touches[0].clientY, })
总结
写博客应该 先本地开启看看,不然内容太乱。。。
计数 + 状态 + 数据集 + 事件 判断依据!
先定义好数据集,考虑的是全局变量 的使用! 计数 - 从0到1,累加通过render来实现,不同数值,对应不同的状态! 状态 - 用于判断的依据!累加到一定程度,进入对应的状态 - 状态转换机??? 事件 - 主要用于修改状态 - 起开关作用 - 在render中进行判断!
这里还没有仔细分析,里面的参数,多少到多少,转的角度,移动距离,时间长短,边界条件。。
如何保存数据 - userData,写入该属性。 如何获取模型中的某一部分 - 获取实例 - sheepGroup.getObjectByName(‘corps’); 如何控制模型变换 - 什么时候,变换那些地方,这么重置 - 还需要再看看。。 如何计数 - 在循环中累加 1 即可,不同范围对应不同状态 如何控制状态 - && ! 字符串
再写几个!很有收获的案例!!!