Threejs-Jumping sheep

源码地址-codepen
Threejs框架,使用JS控制背景变换,镜头移动,控制模型变换等。

01

预备知识

// 获取gltf 对象实例! - 可修改
corps = sheepGroup.getObjectByName(‘corps’);
head = sheepGroup.getObjectByName(‘Head’);

功能实现

  1. 鼠标移动,头部跟着移动
  2. 眨眼动画效果实现
  3. 点击跳跃-实现鼠标、手机、按键的实现
  4. 跳跃之前的动画实现 - 下蹲,脸部变色
  5. 跳跃中的动画实现 - 跃起角度变换
  6. 跳跃不同高度,显示不同的背景 - 背景变换
  7. 超出跳跃的高度,触发拉便便效果,同时改变镜头。。。- 计数实现

代码细节

加载资源

  1. three.js - 核心库
  2. GLTFLoader.js - 加载模型
  3. BufferGeometryUtils - 模型合并操作
  4. 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绑定该位置 - 然后调整参数!
02

通过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 即可,不同范围对应不同状态
如何控制状态 - && ! 字符串

再写几个!很有收获的案例!!!