three-player-controller 0.3.1 → 0.3.3

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 three-player-controller author
3
+ Copyright (c) 2026 屈航
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -73,6 +73,7 @@ export function playerController(): {
73
73
  reset: (pos?: THREE.Vector3) => void;
74
74
  update: (dt?: number) => void;
75
75
  destroy: () => void;
76
+ getposition: () => THREE.Vector3;
76
77
  };
77
78
  ```
78
79
 
@@ -88,6 +89,8 @@ export function playerController(): {
88
89
  每帧调用。
89
90
  - `destroy()`
90
91
  销毁控制器。
92
+ - `getposition()`
93
+ 获取人物当前位置。
91
94
 
92
95
  ---
93
96
 
@@ -139,6 +142,9 @@ type PlayerControllerOptions = {
139
142
  minCamDistance?: number;
140
143
  maxCamDistance?: number;
141
144
  colliderMeshUrl?: string;
145
+ isShowMobileControls?: boolean;
146
+ thirdMouseMode?: number;
147
+ enableZoom?: boolean;
142
148
  };
143
149
  ```
144
150
 
@@ -161,6 +167,7 @@ type PlayerControllerOptions = {
161
167
  | `colliderMeshUrl` | `string` | 自制碰撞体模型路径,默认`""` |
162
168
  | `isShowMobileControls` | `boolean` | 移动端运行时,是否自动显示移动端控制器,默认`true` |
163
169
  | `thirdMouseMode` | `[0, 1, 2, 3]` | 第三人称视角下的不同鼠标控制模式 ,默认`1`(0: 隐藏鼠标控制朝向及视角,1: 隐藏鼠标仅控制视角,2: 显示鼠标拖拽控制朝向及视角, 3: 显示鼠标拖拽仅控制视角) |
170
+ | `enableZoom` | `boolean` | 第三人称模式下是否允许缩放,默认`false` |
164
171
 
165
172
  ---
166
173
 
package/dist/index.d.mts CHANGED
@@ -29,6 +29,7 @@ type PlayerControllerOptions = {
29
29
  colliderMeshUrl?: string;
30
30
  isShowMobileControls?: boolean;
31
31
  thirdMouseMode?: 0 | 1 | 2 | 3;
32
+ enableZoom?: boolean;
32
33
  };
33
34
  declare function playerController(): {
34
35
  init: (opts: PlayerControllerOptions, callback?: () => void) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ type PlayerControllerOptions = {
29
29
  colliderMeshUrl?: string;
30
30
  isShowMobileControls?: boolean;
31
31
  thirdMouseMode?: 0 | 1 | 2 | 3;
32
+ enableZoom?: boolean;
32
33
  };
33
34
  declare function playerController(): {
34
35
  init: (opts: PlayerControllerOptions, callback?: () => void) => Promise<void>;
package/dist/index.js CHANGED
@@ -60,7 +60,6 @@ var PlayerController = class {
60
60
  constructor() {
61
61
  // ==================== 基本配置与参数 ====================
62
62
  this.loader = new import_GLTFLoader.GLTFLoader();
63
- // 0: 普通 1: 飞行 2: 车辆
64
63
  // ==================== 玩家基本属性 ====================
65
64
  this.playerRadius = 45;
66
65
  this.playerHeight = 180;
@@ -351,6 +350,7 @@ var PlayerController = class {
351
350
  this._maxCamDistance = (opts.maxCamDistance ?? 440) * s;
352
351
  this.orginMaxCamDistance = this._maxCamDistance;
353
352
  this.thirdMouseMode = opts.thirdMouseMode ?? 1;
353
+ this.enableZoom = opts.enableZoom ?? false;
354
354
  const isMobileDevice = () => navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
355
355
  this.isShowMobileControls = (opts.isShowMobileControls ?? true) && isMobileDevice();
356
356
  if (this.isShowMobileControls) {
@@ -561,6 +561,7 @@ var PlayerController = class {
561
561
  this.player.attach(this.camera);
562
562
  this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
563
563
  this.camera.rotation.set(0, Math.PI, 0);
564
+ this.controls.enableZoom = false;
564
565
  } else {
565
566
  this.scene.attach(this.camera);
566
567
  const worldPos = this.player.position.clone();
@@ -569,6 +570,7 @@ var PlayerController = class {
569
570
  const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, 200 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
570
571
  this.camera.position.copy(worldPos).add(offset);
571
572
  this.controls.target.copy(worldPos);
573
+ this.controls.enableZoom = this.enableZoom;
572
574
  }
573
575
  this.setPointerLock();
574
576
  }
@@ -582,6 +584,8 @@ var PlayerController = class {
582
584
  setPointerLock() {
583
585
  if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
584
586
  document.body.requestPointerLock();
587
+ } else {
588
+ document.exitPointerLock();
585
589
  }
586
590
  }
587
591
  /**
@@ -603,9 +607,10 @@ var PlayerController = class {
603
607
  * 设置控制器
604
608
  */
605
609
  setControls() {
606
- this.controls.enabled = !(this.thirdMouseMode === 0 || this.thirdMouseMode === 1);
610
+ this.controls.enableZoom = this.enableZoom;
607
611
  this.controls.rotateSpeed = this.mouseSensity * 0.05;
608
612
  this.controls.maxPolarAngle = Math.PI * (300 / 360);
613
+ this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
609
614
  }
610
615
  /**
611
616
  * 重置控制器
@@ -648,6 +653,61 @@ var PlayerController = class {
648
653
  }
649
654
  }
650
655
  // ==================== 物理与碰撞检测 ====================
656
+ /**
657
+ * 统一属性集合
658
+ */
659
+ unifiedAttribute(collected) {
660
+ const attrMap = /* @__PURE__ */ new Map();
661
+ const attrConflict = /* @__PURE__ */ new Set();
662
+ const requiredAttrs = /* @__PURE__ */ new Set(["position", "normal", "uv"]);
663
+ for (const g of collected) {
664
+ const attrNames2 = Object.keys(g.attributes);
665
+ for (const name of attrNames2) {
666
+ if (!requiredAttrs.has(name)) {
667
+ g.deleteAttribute(name);
668
+ }
669
+ }
670
+ }
671
+ for (const g of collected) {
672
+ for (const name of Object.keys(g.attributes)) {
673
+ const attr = g.attributes[name];
674
+ const ctor = attr.array.constructor;
675
+ const itemSize = attr.itemSize;
676
+ const normalized = attr.normalized;
677
+ if (!attrMap.has(name)) {
678
+ attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1, normalized });
679
+ } else {
680
+ const m = attrMap.get(name);
681
+ if (m.itemSize !== itemSize || m.arrayCtor !== ctor || m.normalized !== normalized) {
682
+ attrConflict.add(name);
683
+ } else {
684
+ m.examples++;
685
+ }
686
+ }
687
+ }
688
+ }
689
+ if (attrConflict.size) {
690
+ for (const g of collected) {
691
+ for (const name of Array.from(attrConflict)) {
692
+ if (g.attributes[name]) g.deleteAttribute(name);
693
+ }
694
+ }
695
+ for (const name of attrConflict) attrMap.delete(name);
696
+ }
697
+ const attrNames = Array.from(attrMap.keys());
698
+ for (const g of collected) {
699
+ const count = g.attributes.position.count;
700
+ for (const name of attrNames) {
701
+ if (!g.attributes[name]) {
702
+ const meta = attrMap.get(name);
703
+ const len = count * meta.itemSize;
704
+ const array = new meta.arrayCtor(len);
705
+ g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize, meta.normalized));
706
+ }
707
+ }
708
+ }
709
+ return collected;
710
+ }
651
711
  /**
652
712
  * BVH碰撞体构建
653
713
  */
@@ -663,7 +723,7 @@ var PlayerController = class {
663
723
  }
664
724
  return geom;
665
725
  };
666
- const collected = [];
726
+ let collected = [];
667
727
  if (meshUrl === "") {
668
728
  if (this.collider) {
669
729
  this.scene.remove(this.collider);
@@ -684,45 +744,7 @@ var PlayerController = class {
684
744
  }
685
745
  });
686
746
  if (!collected.length) return;
687
- const attrMap = /* @__PURE__ */ new Map();
688
- const attrConflict = /* @__PURE__ */ new Set();
689
- for (const g of collected) {
690
- for (const name of Object.keys(g.attributes)) {
691
- const attr = g.attributes[name];
692
- const ctor = attr.array.constructor;
693
- const itemSize = attr.itemSize;
694
- if (!attrMap.has(name)) {
695
- attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1 });
696
- } else {
697
- const m = attrMap.get(name);
698
- if (m.itemSize !== itemSize || m.arrayCtor !== ctor) {
699
- attrConflict.add(name);
700
- } else {
701
- m.examples++;
702
- }
703
- }
704
- }
705
- }
706
- if (attrConflict.size) {
707
- for (const g of collected) {
708
- for (const name of Array.from(attrConflict)) {
709
- if (g.attributes[name]) g.deleteAttribute(name);
710
- }
711
- }
712
- for (const name of attrConflict) attrMap.delete(name);
713
- }
714
- const attrNames = Array.from(attrMap.keys());
715
- for (const g of collected) {
716
- const count = g.attributes.position.count;
717
- for (const name of attrNames) {
718
- if (!g.attributes[name]) {
719
- const meta = attrMap.get(name);
720
- const len = count * meta.itemSize;
721
- const array = new meta.arrayCtor(len);
722
- g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize));
723
- }
724
- }
725
- }
747
+ collected = this.unifiedAttribute(collected);
726
748
  } else {
727
749
  const gltf = await this.loader.loadAsync(meshUrl);
728
750
  const mesh = gltf.scene.children[0];
@@ -912,28 +934,30 @@ var PlayerController = class {
912
934
  this.controls.target.copy(lookTarget);
913
935
  this.camera.position.add(lookTarget);
914
936
  this.controls.update();
915
- this._personToCam.subVectors(this.camera.position, this.player.position);
916
- const origin = this.player.position.clone().add(new THREE.Vector3(0, 0, 0));
917
- const direction = this._personToCam.clone().normalize();
918
- const desiredDist = this._personToCam.length();
919
- this._raycasterPersonToCam.set(origin, direction);
920
- this._raycasterPersonToCam.far = desiredDist;
921
- const intersects2 = this._raycasterPersonToCam.intersectObject(this.collider, false);
922
- if (intersects2.length > 0) {
923
- const hit = intersects2[0];
924
- const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
925
- const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
926
- this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
927
- } else {
928
- this._raycasterPersonToCam.far = this._maxCamDistance;
929
- const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(this.collider, false);
930
- let safeDist = this._maxCamDistance;
931
- if (intersectsMaxDis.length) {
932
- const hitMax = intersectsMaxDis[0];
933
- safeDist = hitMax.distance - this._camEpsilon;
937
+ if (!this.enableZoom) {
938
+ this._personToCam.subVectors(this.camera.position, this.player.position);
939
+ const origin = this.player.position.clone().add(new THREE.Vector3(0, 0, 0));
940
+ const direction = this._personToCam.clone().normalize();
941
+ const desiredDist = this._personToCam.length();
942
+ this._raycasterPersonToCam.set(origin, direction);
943
+ this._raycasterPersonToCam.far = desiredDist;
944
+ const intersects2 = this._raycasterPersonToCam.intersectObject(this.collider, false);
945
+ if (intersects2.length > 0) {
946
+ const hit = intersects2[0];
947
+ const safeDist = Math.max(hit.distance - this._camEpsilon, this._minCamDistance);
948
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
949
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
950
+ } else {
951
+ this._raycasterPersonToCam.far = this._maxCamDistance;
952
+ const intersectsMaxDis = this._raycasterPersonToCam.intersectObject(this.collider, false);
953
+ let safeDist = this._maxCamDistance;
954
+ if (intersectsMaxDis.length) {
955
+ const hitMax = intersectsMaxDis[0];
956
+ safeDist = hitMax.distance - this._camEpsilon;
957
+ }
958
+ const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
959
+ this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
934
960
  }
935
- const targetCamPos = origin.clone().add(direction.clone().multiplyScalar(safeDist));
936
- this.camera.position.lerp(targetCamPos, this._camCollisionLerp);
937
961
  }
938
962
  }
939
963
  if (this.player.position.y < this.boundingBoxMinY - 1) {