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 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 碰撞检测、人物动画、第一/三人称切换与相机避障。此仓库包含库源码、example 演示。
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
- [glbScene](https://hh-hang.github.io/three-player-controller/index.html)
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
- - 默认处理包括:WASD 移动、奔跑、跳跃、鼠标视角等。
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` | 基准速度,默认约 `4.0` |
145
- | `playerModel.gravity` | `number` | 重力加速度,默认 `9.8` |
146
- | `playerModel.jumpHeight` | `number` | 跳跃高度 |
147
- | `initPos` | `THREE.Vector3` | 初始位置,默认 `(0,0,0)` |
148
- | `mouseSensity` | `number` | 鼠标灵敏度 |
149
- | `minCamDistance` / `maxCamDistance` | `number` | 第三人称相机距离限制 |
150
- | `colliderMeshUrl` | `string?` | 自制碰撞体模型路径 |
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
- if (document.pointerLockElement !== document.body)
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.isShowMobileControls = opts.isShowMobileControls ?? true;
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
- if (isMobileDevice() && this.isShowMobileControls) {
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
- document.body.requestPointerLock();
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
- document.body.requestPointerLock();
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
- 200 * this.playerModel.scale,
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.controls.enabled = false;
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 && this.moveDir.lengthSq() > 0 && !this.isFlying) {
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
- const lookTarget = this.player.position.clone().add(this.moveDir);
713
- this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
714
- this.targetQuat.setFromRotationMatrix(this.targetMat);
715
- const alpha = Math.min(1, this.rotationSpeed * delta);
716
- this.player.quaternion.slerp(this.targetQuat, alpha);
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
- document.body.requestPointerLock();
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
- const mod = (await import("nipplejs")).default;
1045
- const nipple = mod;
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");