【可视化学习】49-yuka自动导航避障AI库
发表于:2023-10-02 |

前言

本篇文章将给大家介绍yuka这个库,yuka是一个用于游戏开发的AI库,它可以帮助我们快速的实现游戏中的AI功能,比如自动导航、避障、寻路等功能,它是一个非常强大的库,本篇文章将给大家介绍yuka的基本使用方法。

安装yuka

1
yarn add yuka

利用yuka实现路径跟随

初始化代码以及创建地面

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
<script setup>
import * as THREE from "three";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

requestAnimationFrame(function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

</script>

<template>
<div></div>
</template>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
}
</style>

效果图

创建锥体

1
2
3
4
5
6
7
8
const coneGeometry = new THREE.ConeGeometry(0.2, 1, 32);
coneGeometry.rotateX(Math.PI / 2);
const coneMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const cone = new THREE.Mesh(coneGeometry, coneMaterial);
cone.matrixAutoUpdate = false;
cone.receiveShadow = true;
cone.castShadow = true;
scene.add(cone);

效果图

创建yuka车辆

1
2
3
4
5
6
7
8
9
10
11
// 创建yuka的车辆
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 5;
// 设置车辆的渲染对象
vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
console.log(entity, renderComponent);
// renderComponent.position.copy(entity.position);
// renderComponent.quaternion.copy(entity.rotation);
renderComponent.matrix.copy(entity.worldMatrix);
}

这里的renderComponent.matrix.copy(entity.worldMatrix);需要结合上面的cone.matrixAutoUpdate = false;使用,如果不用这俩的话可以用

1
2
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);

进行替代,本质都是一样的,只是matrix的精度更高一些。

创建yuka路径

1
2
3
4
5
6
7
8
9
// 创建yuka的路径
const path = new YUKA.Path();
path.add(new YUKA.Vector3(0, 0, 0));
path.add(new YUKA.Vector3(0, 0, 10));
path.add(new YUKA.Vector3(10, 0, 10));
path.add(new YUKA.Vector3(10, 0, 0));
path.add(new YUKA.Vector3(0, 0, 0));
// 设置路径的循环模式
path.loop = true;

将路径当前的位置设置为车辆的位置

1
2
// 将路径当前的位置设置为车辆的位置
vehicle.position.copy(path.current());

跟随路径的行为

1
2
3
// 跟随路径的行为
const followPathBehavior = new YUKA.FollowPathBehavior(path);
vehicle.steering.add(followPathBehavior);

创建对实体管理对象

1
2
3
// 创建对实体管理对象
const entityManager = new YUKA.EntityManager();
entityManager.add(vehicle);

更新实体管理对象

1
2
3
4
5
6
7
8
const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

展示路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
showPathLine(path);
function showPathLine(path) {
console.log(path);
const positions = [];
for (let i = 0; i < path._waypoints.length; i++) {
positions.push(
path._waypoints[i].x,
path._waypoints[i].y,
path._waypoints[i].z
);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
const material = new THREE.LineBasicMaterial({ color: 0x0000ff });
const line = new THREE.Line(geometry, material);
scene.add(line);
}

完整代码

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<script setup>
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

// 创建椎体
const coneGeometry = new THREE.ConeGeometry(0.2, 1, 32);
coneGeometry.rotateX(Math.PI / 2);
const coneMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const cone = new THREE.Mesh(coneGeometry, coneMaterial);
cone.matrixAutoUpdate = false;
cone.receiveShadow = true;
cone.castShadow = true;
scene.add(cone);

// 创建yuka的车辆
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 5;
// 设置车辆的渲染对象
vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
console.log(entity, renderComponent);
// renderComponent.position.copy(entity.position);
// renderComponent.quaternion.copy(entity.rotation);
renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建yuka的路径
const path = new YUKA.Path();
path.add(new YUKA.Vector3(0, 0, 0));
path.add(new YUKA.Vector3(0, 0, 10));
path.add(new YUKA.Vector3(10, 0, 10));
path.add(new YUKA.Vector3(10, 0, 0));
path.add(new YUKA.Vector3(0, 0, 0));
// 设置路径的循环模式
path.loop = true;

// 将路径当前的位置设置为车辆的位置
vehicle.position.copy(path.current());

// 跟随路径的行为
const followPathBehavior = new YUKA.FollowPathBehavior(path);
vehicle.steering.add(followPathBehavior);

// 创建对实体管理对象
const entityManager = new YUKA.EntityManager();
entityManager.add(vehicle);

showPathLine(path);
function showPathLine(path) {
console.log(path);
const positions = [];
for (let i = 0; i < path._waypoints.length; i++) {
positions.push(
path._waypoints[i].x,
path._waypoints[i].y,
path._waypoints[i].z
);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
const material = new THREE.LineBasicMaterial({ color: 0x0000ff });
const line = new THREE.Line(geometry, material);
scene.add(line);
}
</script>

<template>
<div></div>
</template>

<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

canvas {
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
}
</style>

限制路径与加载模型

限制路径

代码依旧是基于上面的代码,将路径行为添加第二个参数限制偏移,设置下权重weight即可

1
2
3
const onPathBehavior = new YUKA.OnPathBehavior(path, 0.1);
onPathBehavior.weight = 10;
vehicle.steering.add(onPathBehavior);

加载模型

将创建锥体代码注释掉

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
// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
loader.load("./model/car.gltf", function (gltf) {
console.log(gltf);
const car = gltf.scene;
car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.2, 0.2, 0.2);
scene.add(car);
vehicle.setRenderComponent(car, callback);
});
// 创建yuka的车辆
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 5;
// 设置车辆的渲染对象
// vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

搜索目标前进与抵达目标

这里我创建了一个目标小球,并且让他的位置一直变化,给小车添加了到达事件

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
// 创建目标小球
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.receiveShadow = true;
sphere.castShadow = true;
scene.add(sphere);
// 创建目标
const target = new YUKA.GameEntity();
target.setRenderComponent(sphere, callback);

target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);

const entityManager = new YUKA.EntityManager();
entityManager.add(vehicle);
entityManager.add(target);

// 到达行为
const arriveBehavior = new YUKA.ArriveBehavior(target.position);
vehicle.steering.add(arriveBehavior);

// 搜索目标行为
// const seekBehavior = new YUKA.SeekBehavior(target.position);
// vehicle.steering.add(seekBehavior);

setInterval(() => {
target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
}, 2000);

点击前往与避开障碍

点击前往

将上面代码的定时器给清除,并且根据点击事件调整小球的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
// 点击将目标移动到点击的位置
const ndc = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
window.addEventListener("pointerdown", (event) => {
ndc.x = (event.clientX / window.innerWidth) * 2 - 1;
ndc.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(ndc, camera);
const intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
const point = intersects[0].point;
target.position.set(point.x, 0, point.z);
}
});

添加障碍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建障碍物
const obstacles = [];

for (let i = 0; i < 5; i++) {
const boxGeometry = new THREE.BoxGeometry(3, 3, 3);
const boxMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.set(Math.random() * 30 - 15, 0, Math.random() * 30 - 5);
box.receiveShadow = true;
box.castShadow = true;
scene.add(box);
// 创建障碍物
const obstacle = new YUKA.GameEntity();
obstacle.position.copy(box.position);

// 设置障碍物半径
boxGeometry.computeBoundingSphere();
obstacle.boundingRadius = boxGeometry.boundingSphere.radius;
obstacles.push(obstacle);

entityManager.add(obstacle);
}

添加避障行为

从上面的效果可以看到,我们的小车直接无视障碍物传了过去,现在我们给小车加上避障行为

1
2
3
const obstacleAvoidanceBehavior = new YUKA.ObstacleAvoidanceBehavior(obstacles);
vehicle.steering.add(obstacleAvoidanceBehavior);
vehicle.smoother = new YUKA.Smoother(30);

逃离行为

基于上面的代码,去除到达行为,添加逃离行为

1
2
3
// 逃离行为
const fleeBehavior = new YUKA.FleeBehavior(target.position, 3);
vehicle.steering.add(fleeBehavior);

追击行为

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
let c1, c2;
c1 = loader.loadAsync("./model/car.gltf");
c2 = loader.loadAsync("./model/truck.gltf");
Promise.all([c1, c2]).then((res) => {
const car = res[0].scene;
const truck = res[1].scene;
car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.2, 0.2, 0.2);
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 3;
vehicle.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
vehicle.setRenderComponent(car, callback);
entityManager.add(vehicle);

truck.children[0].rotation.y = Math.PI / 2;
truck.children[0].scale.set(0.2, 0.2, 0.2);
const vehicle2 = new YUKA.Vehicle();
vehicle2.maxSpeed = 6;
vehicle2.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
vehicle2.setRenderComponent(truck, callback);
entityManager.add(vehicle2);

scene.add(car);
scene.add(truck);

// 设置追击行为
const pursuitBehavior = new YUKA.PursuitBehavior(vehicle2, 5);
vehicle.steering.add(pursuitBehavior);

// 设置卡车到达目标行为
const arriveBehavior = new YUKA.ArriveBehavior(target.position);
vehicle2.steering.add(arriveBehavior);

setInterval(() => {
target.position.set(Math.random() * 50 - 25, 0, Math.random() * 50 - 25);
}, 3000);
});
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建目标小球
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.receiveShadow = true;
sphere.castShadow = true;
scene.add(sphere);
// 创建目标
const target = new YUKA.GameEntity();
target.setRenderComponent(sphere, callback);

target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);

const entityManager = new YUKA.EntityManager();
entityManager.add(target);

偏离追击效果

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

// 创建椎体
// const coneGeometry = new THREE.ConeGeometry(0.2, 1, 32);
// coneGeometry.rotateX(Math.PI / 2);
// const coneMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
// const cone = new THREE.Mesh(coneGeometry, coneMaterial);
// // cone.matrixAutoUpdate = false;
// cone.receiveShadow = true;
// cone.castShadow = true;
// scene.add(cone);

// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
let c1, c2;
c1 = loader.loadAsync("./model/car.gltf");
c2 = loader.loadAsync("./model/truck.gltf");
Promise.all([c1, c2]).then((res) => {
const truck = res[1].scene;
truck.children[0].rotation.y = Math.PI / 2;
truck.children[0].scale.set(0.2, 0.2, 0.2);
const vehicle2 = new YUKA.Vehicle();
vehicle2.maxSpeed = 4;
vehicle2.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
vehicle2.setRenderComponent(truck, callback);
entityManager.add(vehicle2);

scene.add(truck);

// 设置追击行为
// const pursuitBehavior = new YUKA.PursuitBehavior(vehicle2);
// vehicle.steering.add(pursuitBehavior);

const offsets = [
new YUKA.Vector3(0, 0, 1),
new YUKA.Vector3(-1, 0, 1),
new YUKA.Vector3(1, 0, 1),
new YUKA.Vector3(3, 0, -3),
new YUKA.Vector3(-3, 0, -3),
];

for (let i = 0; i < offsets.length; i++) {
const car = res[0].scene.clone();

car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.2, 0.2, 0.2);
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 6;
vehicle.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
vehicle.setRenderComponent(car, callback);
entityManager.add(vehicle);
scene.add(car);
const offsetPursuitBehavior = new YUKA.OffsetPursuitBehavior(
vehicle2,
offsets[i]
);
vehicle.steering.add(offsetPursuitBehavior);
}

// 偏移跟随
// const offsetPursuitBehavior = new YUKA.OffsetPursuitBehavior(
// vehicle2,
// new YUKA.Vector3(0, 0, 1)
// );
// vehicle.steering.add(offsetPursuitBehavior);

// 设置卡车到达目标行为
const arriveBehavior = new YUKA.ArriveBehavior(target.position);
vehicle2.steering.add(arriveBehavior);

setInterval(() => {
target.position.set(Math.random() * 50 - 25, 0, Math.random() * 50 - 25);
}, 3000);
});
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建目标小球
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.receiveShadow = true;
sphere.castShadow = true;
scene.add(sphere);
// 创建目标
const target = new YUKA.GameEntity();
target.setRenderComponent(sphere, callback);

target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);

const entityManager = new YUKA.EntityManager();
entityManager.add(target);

候鸟集群聚散效果

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.receiveShadow = true;
plane.rotation.x = -Math.PI / 2;
plane.position.y = -0.5;
scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

// 创建椎体
// const coneGeometry = new THREE.ConeGeometry(0.2, 1, 32);
// coneGeometry.rotateX(Math.PI / 2);
// const coneMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
// const cone = new THREE.Mesh(coneGeometry, coneMaterial);
// // cone.matrixAutoUpdate = false;
// cone.receiveShadow = true;
// cone.castShadow = true;
// scene.add(cone);

// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
loader.load("./model/car.gltf", function (gltf) {
// console.log(gltf);
// 创建群体随机行走行为
const wanderBehavior = new YUKA.WanderBehavior(3);

// 设置整齐群体转向
const alignmentBehavior = new YUKA.AlignmentBehavior();
alignmentBehavior.weight = 5;

// 设置聚集行为
const cohesionBehavior = new YUKA.CohesionBehavior();
cohesionBehavior.weight = 5;

// 设置分离行为
const separationBehavior = new YUKA.SeparationBehavior();
separationBehavior.weight = 0.5;

for (let i = 0; i < 40; i++) {
// 创建yuka的车辆
const vehicle = new YUKA.Vehicle();
vehicle.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
vehicle.rotation.fromEuler(0, Math.random() * Math.PI, 0);
vehicle.maxSpeed = 3;
const car = gltf.scene.clone();
car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.2, 0.2, 0.2);
scene.add(car);
vehicle.setRenderComponent(car, callback);
entityManager.add(vehicle);

vehicle.steering.add(wanderBehavior);
vehicle.steering.add(alignmentBehavior);
vehicle.steering.add(cohesionBehavior);
vehicle.steering.add(separationBehavior);
}
});

// 设置车辆的渲染对象
// vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建对实体管理对象
const entityManager = new YUKA.EntityManager();

导航网格自动导航

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建地面
// const planeGeometry = new THREE.PlaneGeometry(50, 50);
// const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
// const plane = new THREE.Mesh(planeGeometry, planeMaterial);
// plane.receiveShadow = true;
// plane.rotation.x = -Math.PI / 2;
// plane.position.y = -0.5;
// scene.add(plane);

// 创建灯光
const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
light.position.set(10, 40, 10);
light.castShadow = true;
scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);

if (navMesh) {
const currentRegion = navMesh.getRegionForPoint(vehicle.position);
if (currentRegion) {
const distance = currentRegion.distanceToPoint(vehicle.position);
vehicle.position.y -= distance * 0.2;
}
}
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);
loader.load("./model/car.gltf", function (gltf) {
console.log(gltf);
const car = gltf.scene;
car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.2, 0.2, 0.2);
scene.add(car);
vehicle.setRenderComponent(car, callback);
});
let plane;
loader.load("./model/modelMap.gltf", function (gltf) {
console.log(gltf);
plane = gltf.scene;
plane.traverse((child) => {
if (child.isMesh) {
child.receiveShadow = true;
child.castShadow = true;
}
});
scene.add(plane);
});

// 创建yuka的车辆
const vehicle = new YUKA.Vehicle();
vehicle.maxSpeed = 5;
// 设置车辆的渲染对象
// vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建目标小球
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.receiveShadow = true;
sphere.castShadow = true;
scene.add(sphere);
// 创建目标
const target = new YUKA.GameEntity();
target.setRenderComponent(sphere, callback);

target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);

const entityManager = new YUKA.EntityManager();
entityManager.add(vehicle);
entityManager.add(target);

// 到达行为
// const arriveBehavior = new YUKA.ArriveBehavior(target.position);
// vehicle.steering.add(arriveBehavior);

// 搜索目标行为
// const seekBehavior = new YUKA.SeekBehavior(target.position);
// vehicle.steering.add(seekBehavior);

// setInterval(() => {
// target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
// }, 2000);
let line;
function showPathLine(path) {
if (line) {
scene.remove(line);
line.geometry.dispose();
line.material.dispose();
}
// console.log(path);
const positions = [];
for (let i = 0; i < path._waypoints.length; i++) {
positions.push(
path._waypoints[i].x,
path._waypoints[i].y,
path._waypoints[i].z
);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
const material = new THREE.LineBasicMaterial({ color: 0x0000ff });
line = new THREE.Line(geometry, material);
scene.add(line);
}

// 点击将目标移动到点击的位置
const ndc = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
window.addEventListener("pointerdown", (event) => {
ndc.x = (event.clientX / window.innerWidth) * 2 - 1;
ndc.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(ndc, camera);
const intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
const point = intersects[0].point;
target.position.set(point.x, 0, point.z);
let from = vehicle.position;
let to = point;

// 根据导航网格获取路径
console.log(navMesh);
const path = navMesh.findPath(from, to);
// console.log(path);
const path1 = new YUKA.Path();
for (let item of path) {
path1.add(new YUKA.Vector3(item.x, item.y, item.z));
}
showPathLine(path1);
vehicle.steering.clear();
// const followPathBehavior = new YUKA.FollowPathBehavior(path1);
// followPathBehavior.weight = 10;
// vehicle.steering.add(followPathBehavior);

const onPathBehavior = new YUKA.OnPathBehavior(path1, 0.1, 0.1);
onPathBehavior.weight = 10;
vehicle.steering.add(onPathBehavior);

const arriveBehavior = new YUKA.ArriveBehavior(to, 3, 0.1);
arriveBehavior.weight = 1;
vehicle.steering.add(arriveBehavior);
}
});

vehicle.smoother = new YUKA.Smoother(30);

// 网格加载器
const navMeshLoader = new YUKA.NavMeshLoader();
// 加载网格
let navMesh;
navMeshLoader.load("./model/modelMap.gltf").then(function (navigationMesh) {
console.log(navigationMesh);
navMesh = navigationMesh;
});

添加真实模型的导航网格

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import * as THREE from "three";
import * as YUKA from "yuka";
// 导入控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
import { Reflector } from "./mesh/Reflector.js";

// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 10, 20);
camera.lookAt(0, 0, 0);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
document.body.appendChild(renderer.domElement);

// 创建地面
// const planeGeometry = new THREE.PlaneGeometry(50, 50);
// const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
// const plane = new THREE.Mesh(planeGeometry, planeMaterial);
// plane.receiveShadow = true;
// plane.rotation.x = -Math.PI / 2;
// plane.position.y = -0.5;
// scene.add(plane);

// 创建灯光
// const light = new THREE.SpotLight(0xffffff, 3, 100, Math.PI / 6, 0.5);
// light.position.set(10, 40, 10);
// light.castShadow = true;
// scene.add(light);

// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const time = new YUKA.Time();
requestAnimationFrame(function animate() {
const delta = time.update().getDelta();
controls.update();
entityManager.update(delta);

// if (navMesh) {
// const currentRegion = navMesh.getRegionForPoint(vehicle.position);
// if (currentRegion) {
// const distance = currentRegion.distanceToPoint(vehicle.position);
// vehicle.position.y -= distance * 0.2;
// }
// }
renderer.render(scene, camera);
requestAnimationFrame(animate);
});

class CustomVechicle extends YUKA.Vehicle {
constructor(navMesh) {
super();
this.navMesh = navMesh;
}
update(delta) {
super.update(delta);
const currentRegion = this.navMesh.getRegionForPoint(this.position);
if (currentRegion) {
const distance = currentRegion.distanceToPoint(this.position);
this.position.y -= distance * 0.2;
}
}
}

// 加载模型
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("./draco/");
loader.setDRACOLoader(dracoLoader);

let plane;
loader.load("./model/city.glb", function (gltf) {
let city = gltf.scene;
plane = city;
scene.add(city);
});

// 创建yuka的车辆
// const vehicle = new YUKA.Vehicle();

// 设置车辆的渲染对象
// vehicle.setRenderComponent(cone, callback);
function callback(entity, renderComponent) {
// console.log(entity, renderComponent);
renderComponent.position.copy(entity.position);
renderComponent.quaternion.copy(entity.rotation);
// renderComponent.matrix.copy(entity.worldMatrix);
}

// 创建目标小球
const sphereGeometry = new THREE.SphereGeometry(0.1, 32, 32);
const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.receiveShadow = true;
sphere.castShadow = true;
scene.add(sphere);
// 创建目标
const target = new YUKA.GameEntity();
target.setRenderComponent(sphere, callback);

target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);

const entityManager = new YUKA.EntityManager();

entityManager.add(target);

// 到达行为
// const arriveBehavior = new YUKA.ArriveBehavior(target.position);
// vehicle.steering.add(arriveBehavior);

// 搜索目标行为
// const seekBehavior = new YUKA.SeekBehavior(target.position);
// vehicle.steering.add(seekBehavior);

// setInterval(() => {
// target.position.set(Math.random() * 20 - 10, 0, Math.random() * 20 - 10);
// }, 2000);
let line;
function showPathLine(path) {
if (line) {
scene.remove(line);
line.geometry.dispose();
line.material.dispose();
}
// console.log(path);
const positions = [];
for (let i = 0; i < path._waypoints.length; i++) {
positions.push(
path._waypoints[i].x,
path._waypoints[i].y,
path._waypoints[i].z
);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
const material = new THREE.LineBasicMaterial({ color: 0x0000ff });
line = new THREE.Line(geometry, material);
scene.add(line);
}

// 点击将目标移动到点击的位置
const ndc = new THREE.Vector2();
const raycaster = new THREE.Raycaster();
window.addEventListener("pointerdown", (event) => {
ndc.x = (event.clientX / window.innerWidth) * 2 - 1;
ndc.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(ndc, camera);
const intersects = raycaster.intersectObject(plane);
if (intersects.length > 0) {
const point = intersects[0].point;
target.position.set(point.x, 0, point.z);
let from = vehicle.position;
let to = point;

// 根据导航网格获取路径
console.log(navMesh);
const path = navMesh.findPath(from, to);
// console.log(path);
const path1 = new YUKA.Path();
for (let item of path) {
path1.add(new YUKA.Vector3(item.x, item.y, item.z));
}
showPathLine(path1);
vehicle.steering.clear();
// const followPathBehavior = new YUKA.FollowPathBehavior(path1);
// followPathBehavior.weight = 10;
// vehicle.steering.add(followPathBehavior);

const onPathBehavior = new YUKA.OnPathBehavior(path1, 0.1, 0.1);
// onPathBehavior.weight = 10;
vehicle.steering.add(onPathBehavior);

const arriveBehavior = new YUKA.ArriveBehavior(to, 3, 3);
arriveBehavior.weight = 1;
vehicle.steering.add(arriveBehavior);
}
});

// 网格加载器
const navMeshLoader = new YUKA.NavMeshLoader();
// 加载网格
let navMesh, vehicle;
navMeshLoader.load("./model/citymap1.gltf").then(function (navigationMesh) {
console.log(navigationMesh);
navMesh = navigationMesh;

vehicle = new CustomVechicle(navMesh);
vehicle.maxSpeed = 5;
vehicle.smoother = new YUKA.Smoother(30);
entityManager.add(vehicle);
loader.load("./model/robot.glb", function (gltf) {
console.log(gltf);
const car = gltf.scene;
// car.children[0].rotation.y = Math.PI / 2;
car.children[0].scale.set(0.6, 0.6, 0.6);
scene.add(car);
vehicle.setRenderComponent(car, callback);
});
});

// 加载hdr环境贴图
const hdrLoader = new RGBELoader();
hdrLoader.load("./texture/013.hdr", function (texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.background = texture;
scene.environment = texture;
});

let mirrorGeometry = new THREE.PlaneGeometry(200, 100);
let groundMirror = new Reflector(mirrorGeometry, {
clipBias: 0.003,
textureWidth: window.innerWidth * window.devicePixelRatio,
textureHeight: window.innerHeight * window.devicePixelRatio,
color: 0x777777,
});
groundMirror.position.y = 0.1;
groundMirror.rotateX(-Math.PI / 2);
scene.add(groundMirror);

结语

本篇文章就介绍到这里,更多内容敬请期待。

上一篇:
【JAVA学习】01-DOS命令学习
下一篇:
【可视化学习】48-昼夜交替项目