three-player-controller 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -20
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +53 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
[English](README_En.md)
|
|
2
|
+
|
|
1
3
|
# three-player-controller
|
|
2
4
|
|
|
3
|
-
轻量的第三人称 / 第一人称玩家控制器,开箱即用,基于 three.js 和 three-mesh-bvh 实现人物胶囊体碰撞、BVH
|
|
5
|
+
轻量的第三人称 / 第一人称玩家控制器,开箱即用,基于 three.js 和 three-mesh-bvh 实现人物胶囊体碰撞、BVH 碰撞检测、人物动画、第一/三人称切换与相机避障。
|
|
4
6
|
|
|
5
7
|
# 安装
|
|
6
8
|
|
|
9
|
+
```bash
|
|
7
10
|
npm install three-player-controller
|
|
11
|
+
```
|
|
8
12
|
|
|
9
13
|
# 示例
|
|
10
14
|
|
|
11
|
-
[
|
|
15
|
+
[glb 场景](https://hh-hang.github.io/three-player-controller/index.html)
|
|
16
|
+
[3dtiles 场景](https://hh-hang.github.io/three-player-controller/3dtilesScene.html)
|
|
12
17
|
|
|
13
18
|
### 控制
|
|
14
19
|
|
|
@@ -70,12 +75,12 @@ export function playerController(): {
|
|
|
70
75
|
|
|
71
76
|
- `init(opts, callback?)`
|
|
72
77
|
初始化控制器。`callback` 在资源加载完成后调用。
|
|
73
|
-
- `update(dt?)`
|
|
74
|
-
每帧调用。
|
|
75
78
|
- `changeView()`
|
|
76
79
|
在第一/第三人称间切换。
|
|
77
80
|
- `reset(pos?)`
|
|
78
81
|
复位玩家到指定位置。
|
|
82
|
+
- `update(dt?)`
|
|
83
|
+
每帧调用。
|
|
79
84
|
- `destroy()`
|
|
80
85
|
销毁控制器。
|
|
81
86
|
|
|
@@ -94,7 +99,8 @@ export function offAllEvent(): void; // 关闭所有输入事件
|
|
|
94
99
|
|
|
95
100
|
- `onAllEvent()`:确保控制器存在并打开输入监听。
|
|
96
101
|
- `offAllEvent()`:关闭输入监听(用于显示 UI 或暂停时禁止玩家输入)。
|
|
97
|
-
|
|
102
|
+
|
|
103
|
+
默认处理包括:WASD 移动、奔跑、跳跃、鼠标视角等。
|
|
98
104
|
|
|
99
105
|
---
|
|
100
106
|
|
|
@@ -133,21 +139,23 @@ type PlayerControllerOptions = {
|
|
|
133
139
|
|
|
134
140
|
### 关键字段说明
|
|
135
141
|
|
|
136
|
-
| 字段 | 类型 | 默认 / 说明
|
|
137
|
-
| ------------------------------------------------------------ | ------------------------: |
|
|
138
|
-
| `scene` | `THREE.Scene` | three.js 场景(必填)
|
|
139
|
-
| `camera` | `THREE.PerspectiveCamera` | three.js 相机(必填)
|
|
140
|
-
| `controls` | `OrbitControls` | 外部相机控制器(必填)
|
|
141
|
-
| `playerModel.url` | `string` | 人物模型路径(GLB/GLTF,必填)
|
|
142
|
-
| `playerModel.scale` | `number` | 人物模型缩放(必填)
|
|
143
|
-
| `playerModel.idleAnim` / `walkAnim` / `runAnim` / `jumpAnim` | `string` | 人物动画名,需与人物模型中动画名称一致(必填)
|
|
144
|
-
| `playerModel.speed` | `number` |
|
|
145
|
-
| `playerModel.gravity` | `number` |
|
|
146
|
-
| `playerModel.jumpHeight` | `number` |
|
|
147
|
-
| `initPos` | `THREE.Vector3` |
|
|
148
|
-
| `mouseSensity` | `number` |
|
|
149
|
-
| `minCamDistance` / `maxCamDistance` | `number` |
|
|
150
|
-
| `colliderMeshUrl` |
|
|
142
|
+
| 字段 | 类型 | 默认 / 说明 |
|
|
143
|
+
| ------------------------------------------------------------ | ------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
144
|
+
| `scene` | `THREE.Scene` | three.js 场景(必填) |
|
|
145
|
+
| `camera` | `THREE.PerspectiveCamera` | three.js 相机(必填) |
|
|
146
|
+
| `controls` | `OrbitControls` | 外部相机控制器(必填) |
|
|
147
|
+
| `playerModel.url` | `string` | 人物模型路径(GLB/GLTF,必填) |
|
|
148
|
+
| `playerModel.scale` | `number` | 人物模型缩放(必填) |
|
|
149
|
+
| `playerModel.idleAnim` / `walkAnim` / `runAnim` / `jumpAnim` | `string` | 人物动画名,需与人物模型中动画名称一致(必填) |
|
|
150
|
+
| `playerModel.speed` | `number` | 基准速度,默认`400` |
|
|
151
|
+
| `playerModel.gravity` | `number` | 重力加速度,默认`-2400` |
|
|
152
|
+
| `playerModel.jumpHeight` | `number` | 跳跃高度,默认`800` |
|
|
153
|
+
| `initPos` | `THREE.Vector3` | 初始位置,默认`(0,0,0)` |
|
|
154
|
+
| `mouseSensity` | `number` | 鼠标灵敏度,默认`5` |
|
|
155
|
+
| `minCamDistance` / `maxCamDistance` | `number` | 第三人称相机距离限制,默认分别为`100`、`440` |
|
|
156
|
+
| `colliderMeshUrl` | `string` | 自制碰撞体模型路径,默认`""` |
|
|
157
|
+
| `isShowMobileControls` | `boolean` | 移动端运行时,是否自动显示移动端控制器,默认`true` |
|
|
158
|
+
| `thirdMouseMode` | `[0, 1, 2, 3]` | 第三人称视角下的不同鼠标控制模式 ,默认`1`(0: 隐藏鼠标控制朝向及视角,1: 隐藏鼠标仅控制视角,2: 显示鼠标拖拽控制朝向及视角, 3: 显示鼠标拖拽仅控制视角) |
|
|
151
159
|
|
|
152
160
|
---
|
|
153
161
|
|
package/dist/index.d.mts
CHANGED
|
@@ -22,11 +22,13 @@ type PlayerControllerOptions = {
|
|
|
22
22
|
speed?: number;
|
|
23
23
|
};
|
|
24
24
|
initPos?: THREE.Vector3;
|
|
25
|
+
intDirection?: THREE.Vector3;
|
|
25
26
|
mouseSensity?: number;
|
|
26
27
|
minCamDistance?: number;
|
|
27
28
|
maxCamDistance?: number;
|
|
28
29
|
colliderMeshUrl?: string;
|
|
29
30
|
isShowMobileControls?: boolean;
|
|
31
|
+
thirdMouseMode?: 0 | 1 | 2 | 3;
|
|
30
32
|
};
|
|
31
33
|
declare function playerController(): {
|
|
32
34
|
init: (opts: PlayerControllerOptions, callback?: () => void) => Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -22,11 +22,13 @@ type PlayerControllerOptions = {
|
|
|
22
22
|
speed?: number;
|
|
23
23
|
};
|
|
24
24
|
initPos?: THREE.Vector3;
|
|
25
|
+
intDirection?: THREE.Vector3;
|
|
25
26
|
mouseSensity?: number;
|
|
26
27
|
minCamDistance?: number;
|
|
27
28
|
maxCamDistance?: number;
|
|
28
29
|
colliderMeshUrl?: string;
|
|
29
30
|
isShowMobileControls?: boolean;
|
|
31
|
+
thirdMouseMode?: 0 | 1 | 2 | 3;
|
|
30
32
|
};
|
|
31
33
|
declare function playerController(): {
|
|
32
34
|
init: (opts: PlayerControllerOptions, callback?: () => void) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ var PlayerController = class {
|
|
|
60
60
|
// 射线检测时只返回第一个碰撞
|
|
61
61
|
constructor() {
|
|
62
62
|
this.loader = new import_GLTFLoader.GLTFLoader();
|
|
63
|
+
// 0: 隐藏鼠标控制朝向及视角,1: 隐藏鼠标仅控制视角,2: 显示鼠标拖拽控制朝向及视角, 3: 显示鼠标拖拽仅控制视角
|
|
63
64
|
this.playerRadius = 45;
|
|
64
65
|
this.playerHeight = 180;
|
|
65
66
|
this.isFirstPerson = false;
|
|
@@ -86,6 +87,7 @@ var PlayerController = class {
|
|
|
86
87
|
this.shiftPressed = false;
|
|
87
88
|
// 移动端输入
|
|
88
89
|
this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
|
|
90
|
+
this.nippleModule = null;
|
|
89
91
|
// 移动控制相关
|
|
90
92
|
this.joystickManager = null;
|
|
91
93
|
this.joystickZoneEl = null;
|
|
@@ -165,6 +167,11 @@ var PlayerController = class {
|
|
|
165
167
|
case "ShiftLeft":
|
|
166
168
|
this.shiftPressed = true;
|
|
167
169
|
this.setAnimationByPressed();
|
|
170
|
+
this.controls.mouseButtons = {
|
|
171
|
+
LEFT: 2,
|
|
172
|
+
MIDDLE: 1,
|
|
173
|
+
RIGHT: 0
|
|
174
|
+
};
|
|
168
175
|
break;
|
|
169
176
|
case "Space":
|
|
170
177
|
this.spacePressed = true;
|
|
@@ -211,6 +218,11 @@ var PlayerController = class {
|
|
|
211
218
|
case "ShiftLeft":
|
|
212
219
|
this.shiftPressed = false;
|
|
213
220
|
this.setAnimationByPressed();
|
|
221
|
+
this.controls.mouseButtons = {
|
|
222
|
+
LEFT: 0,
|
|
223
|
+
MIDDLE: 1,
|
|
224
|
+
RIGHT: 2
|
|
225
|
+
};
|
|
214
226
|
break;
|
|
215
227
|
case "Space":
|
|
216
228
|
this.spacePressed = false;
|
|
@@ -280,8 +292,7 @@ var PlayerController = class {
|
|
|
280
292
|
this.setToward(e.movementX, e.movementY, 1e-4);
|
|
281
293
|
};
|
|
282
294
|
this._mouseClick = (e) => {
|
|
283
|
-
|
|
284
|
-
document.body.requestPointerLock();
|
|
295
|
+
this.setPointerLock();
|
|
285
296
|
};
|
|
286
297
|
this.onPointerDown = (e) => {
|
|
287
298
|
if (e.pointerType !== "touch") return;
|
|
@@ -321,6 +332,7 @@ var PlayerController = class {
|
|
|
321
332
|
this.controls = opts.controls;
|
|
322
333
|
this.playerModel = opts.playerModel;
|
|
323
334
|
this.initPos = opts.initPos ? opts.initPos : new THREE.Vector3(0, 0, 0);
|
|
335
|
+
this.intDirection = opts.intDirection ? opts.intDirection : new THREE.Vector3(0, 0, -1);
|
|
324
336
|
this.mouseSensity = opts.mouseSensity ? opts.mouseSensity : 5;
|
|
325
337
|
const s = this.playerModel.scale;
|
|
326
338
|
this.visualizeDepth = 0 * s;
|
|
@@ -333,11 +345,12 @@ var PlayerController = class {
|
|
|
333
345
|
this._minCamDistance = opts.minCamDistance ? opts.minCamDistance * s : 100 * s;
|
|
334
346
|
this._maxCamDistance = opts.maxCamDistance ? opts.maxCamDistance * s : 440 * s;
|
|
335
347
|
this.orginMaxCamDistance = this._maxCamDistance;
|
|
336
|
-
this.
|
|
348
|
+
this.thirdMouseMode = opts.thirdMouseMode ?? 1;
|
|
337
349
|
function isMobileDevice() {
|
|
338
350
|
return navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
339
351
|
}
|
|
340
|
-
|
|
352
|
+
this.isShowMobileControls = (opts.isShowMobileControls ?? true) && isMobileDevice();
|
|
353
|
+
if (this.isShowMobileControls) {
|
|
341
354
|
this.initMobileControls();
|
|
342
355
|
}
|
|
343
356
|
await this.createBVH(opts.colliderMeshUrl);
|
|
@@ -362,7 +375,7 @@ var PlayerController = class {
|
|
|
362
375
|
30 * this.playerModel.scale
|
|
363
376
|
);
|
|
364
377
|
this.camera.rotation.set(0, Math.PI, 0);
|
|
365
|
-
|
|
378
|
+
this.setPointerLock();
|
|
366
379
|
} else {
|
|
367
380
|
this.scene.attach(this.camera);
|
|
368
381
|
const worldPos = this.player.position.clone();
|
|
@@ -377,9 +390,13 @@ var PlayerController = class {
|
|
|
377
390
|
);
|
|
378
391
|
this.camera.position.copy(worldPos).add(offset);
|
|
379
392
|
this.controls.target.copy(worldPos);
|
|
380
|
-
|
|
393
|
+
this.setPointerLock();
|
|
381
394
|
}
|
|
382
395
|
}
|
|
396
|
+
setPointerLock() {
|
|
397
|
+
if ((this.thirdMouseMode == 0 || this.thirdMouseMode == 1) && !this.isFirstPerson || this.isFirstPerson)
|
|
398
|
+
document.body.requestPointerLock();
|
|
399
|
+
}
|
|
383
400
|
// 摄像机/控制器设置
|
|
384
401
|
setCameraPos() {
|
|
385
402
|
if (this.isFirstPerson) {
|
|
@@ -396,7 +413,7 @@ var PlayerController = class {
|
|
|
396
413
|
const angle = Math.atan2(dir.z, dir.x);
|
|
397
414
|
const offset = new THREE.Vector3(
|
|
398
415
|
Math.cos(angle) * 400 * this.playerModel.scale,
|
|
399
|
-
|
|
416
|
+
-100 * this.playerModel.scale,
|
|
400
417
|
Math.sin(angle) * 400 * this.playerModel.scale
|
|
401
418
|
);
|
|
402
419
|
this.camera.position.copy(worldPos).add(offset);
|
|
@@ -405,7 +422,12 @@ var PlayerController = class {
|
|
|
405
422
|
}
|
|
406
423
|
// 设置控制器
|
|
407
424
|
setControls() {
|
|
408
|
-
this.
|
|
425
|
+
if (this.thirdMouseMode == 0 || this.thirdMouseMode == 1) {
|
|
426
|
+
this.controls.enabled = false;
|
|
427
|
+
} else {
|
|
428
|
+
this.controls.enabled = true;
|
|
429
|
+
}
|
|
430
|
+
this.controls.rotateSpeed = this.mouseSensity * 0.05;
|
|
409
431
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
410
432
|
}
|
|
411
433
|
// 重置控制器
|
|
@@ -703,17 +725,31 @@ var PlayerController = class {
|
|
|
703
725
|
const offset = Math.max(0, deltaVector.length() - 1e-5);
|
|
704
726
|
deltaVector.normalize().multiplyScalar(offset);
|
|
705
727
|
this.player.position.add(deltaVector);
|
|
706
|
-
if (!this.isFirstPerson &&
|
|
728
|
+
if (!this.isFirstPerson && !this.isFlying) {
|
|
707
729
|
this.camDir.y = 0;
|
|
708
730
|
this.camDir.normalize();
|
|
709
731
|
this.camDir.negate();
|
|
710
732
|
this.moveDir.normalize();
|
|
711
733
|
this.moveDir.negate();
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
734
|
+
let lookTarget;
|
|
735
|
+
if (this.thirdMouseMode == 0 || this.thirdMouseMode == 2) {
|
|
736
|
+
if (this.moveDir.lengthSq() > 0) {
|
|
737
|
+
lookTarget = this.player.position.clone().add(this.moveDir);
|
|
738
|
+
} else {
|
|
739
|
+
lookTarget = this.player.position.clone().add(this.camDir);
|
|
740
|
+
}
|
|
741
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
742
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
743
|
+
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
744
|
+
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
745
|
+
}
|
|
746
|
+
if ((this.thirdMouseMode == 1 || this.thirdMouseMode == 3) && this.moveDir.lengthSq() > 0) {
|
|
747
|
+
lookTarget = this.player.position.clone().add(this.moveDir);
|
|
748
|
+
this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
|
|
749
|
+
this.targetQuat.setFromRotationMatrix(this.targetMat);
|
|
750
|
+
const alpha = Math.min(1, this.rotationSpeed * delta);
|
|
751
|
+
this.player.quaternion.slerp(this.targetQuat, alpha);
|
|
752
|
+
}
|
|
717
753
|
}
|
|
718
754
|
if (this.isFlying) {
|
|
719
755
|
this.camDir.y = 0;
|
|
@@ -832,7 +868,7 @@ var PlayerController = class {
|
|
|
832
868
|
// 事件绑定
|
|
833
869
|
onAllEvent() {
|
|
834
870
|
this.isupdate = true;
|
|
835
|
-
|
|
871
|
+
this.setPointerLock();
|
|
836
872
|
window.addEventListener("keydown", this._boundOnKeydown);
|
|
837
873
|
window.addEventListener("keyup", this._boundOnKeyup);
|
|
838
874
|
window.addEventListener("mousemove", this._mouseMove);
|
|
@@ -1041,8 +1077,8 @@ var PlayerController = class {
|
|
|
1041
1077
|
async initMobileControls() {
|
|
1042
1078
|
this.controls.maxPolarAngle = Math.PI * (300 / 360);
|
|
1043
1079
|
this.controls.touches = { ONE: null, TWO: null };
|
|
1044
|
-
|
|
1045
|
-
const nipple =
|
|
1080
|
+
this.nippleModule = await import("nipplejs");
|
|
1081
|
+
const nipple = this.nippleModule?.default;
|
|
1046
1082
|
const JOY_SIZE = 120;
|
|
1047
1083
|
const container = document.body;
|
|
1048
1084
|
this.joystickZoneEl = document.createElement("div");
|