three-player-controller 0.3.0 → 0.3.2

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/dist/index.d.mts CHANGED
@@ -38,6 +38,17 @@ declare function playerController(): {
38
38
  destroy: () => void;
39
39
  setInput: (i: any) => void;
40
40
  getposition: () => THREE.Vector3;
41
+ loadVehicleModel: (params: {
42
+ url: string;
43
+ position: THREE.Vector3;
44
+ scale?: number;
45
+ animations: {
46
+ openDoorAnim?: string;
47
+ wheelsTurnAnim?: string;
48
+ turnLeftAnim?: string;
49
+ turnRightAnim?: string;
50
+ };
51
+ }) => Promise<void>;
41
52
  };
42
53
  declare function onAllEvent(): void;
43
54
  declare function offAllEvent(): void;
package/dist/index.d.ts CHANGED
@@ -38,6 +38,17 @@ declare function playerController(): {
38
38
  destroy: () => void;
39
39
  setInput: (i: any) => void;
40
40
  getposition: () => THREE.Vector3;
41
+ loadVehicleModel: (params: {
42
+ url: string;
43
+ position: THREE.Vector3;
44
+ scale?: number;
45
+ animations: {
46
+ openDoorAnim?: string;
47
+ wheelsTurnAnim?: string;
48
+ turnLeftAnim?: string;
49
+ turnRightAnim?: string;
50
+ };
51
+ }) => Promise<void>;
41
52
  };
42
53
  declare function onAllEvent(): void;
43
54
  declare function offAllEvent(): void;
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ var PlayerController = class {
60
60
  constructor() {
61
61
  // ==================== 基本配置与参数 ====================
62
62
  this.loader = new import_GLTFLoader.GLTFLoader();
63
- // 0: 隐藏鼠标控制朝向及视角,1: 隐藏鼠标仅控制视角,2: 显示鼠标拖拽控制朝向及视角, 3: 显示鼠标拖拽仅控制视角
63
+ // 0: 普通 1: 飞行 2: 车辆
64
64
  // ==================== 玩家基本属性 ====================
65
65
  this.playerRadius = 45;
66
66
  this.playerHeight = 180;
@@ -74,6 +74,7 @@ var PlayerController = class {
74
74
  this.collider = null;
75
75
  this.visualizer = null;
76
76
  this.person = null;
77
+ this.vehicle = null;
77
78
  // ==================== 状态开关 ====================
78
79
  this.playerIsOnGround = false;
79
80
  this.isupdate = true;
@@ -236,6 +237,9 @@ var PlayerController = class {
236
237
  this.playPersonAnimationByName("jumping");
237
238
  }
238
239
  break;
240
+ case "KeyE":
241
+ this.setDrive();
242
+ break;
239
243
  }
240
244
  };
241
245
  /**
@@ -503,6 +507,50 @@ var PlayerController = class {
503
507
  this.reset();
504
508
  this.player.rotateY(this.playerModel.rotateY ?? 0);
505
509
  }
510
+ // ==================== 车辆模型相关 ====================
511
+ /**
512
+ * 加载车辆模型与动画
513
+ */
514
+ async loadVehicleModel(params) {
515
+ try {
516
+ const { url, position, scale = 1 } = params;
517
+ const gltf = await this.loader.loadAsync(url);
518
+ this.vehicle = gltf.scene;
519
+ this.vehicle.scale.set(scale, scale, scale);
520
+ this.vehicle.position.copy(position);
521
+ this.vehicle.traverse((child) => {
522
+ if (child.isMesh) {
523
+ child.castShadow = true;
524
+ child.receiveShadow = true;
525
+ }
526
+ });
527
+ this.scene.add(this.vehicle);
528
+ const animations = gltf.animations ?? [];
529
+ this.vehicleActions = /* @__PURE__ */ new Map();
530
+ this.vehicleMixer = new THREE.AnimationMixer(this.vehicle);
531
+ const animationMappings = [
532
+ [params.animations?.openDoorAnim ?? "", "open_door"],
533
+ [params.animations?.wheelsTurnAnim ?? "", "wheels_turn"],
534
+ [params.animations?.turnLeftAnim ?? "", "turn_left"],
535
+ [params.animations?.turnRightAnim ?? "", "turn_right"]
536
+ ];
537
+ const findClip = (name) => animations.find((a) => a.name === name);
538
+ for (const [clipName, actionName] of animationMappings) {
539
+ const clip = findClip(clipName);
540
+ if (!clip) continue;
541
+ const action = this.vehicleMixer.clipAction(clip);
542
+ action.setLoop(THREE.LoopOnce, 1);
543
+ action.clampWhenFinished = true;
544
+ action.setEffectiveTimeScale(2);
545
+ action.enabled = true;
546
+ action.setEffectiveWeight(0);
547
+ this.vehicleActions.set(actionName, action);
548
+ }
549
+ console.log("\u5F00\u95E8\u52A8\u753B", this.vehicleActions.get("open_door"));
550
+ } catch (error) {
551
+ console.error("\u52A0\u8F7D\u8F66\u8F86\u6A21\u578B\u5931\u8D25:", error);
552
+ }
553
+ }
506
554
  // ==================== 相机与视角控制 ====================
507
555
  /**
508
556
  * 第一/三人称视角切换
@@ -524,6 +572,10 @@ var PlayerController = class {
524
572
  }
525
573
  this.setPointerLock();
526
574
  }
575
+ setDrive() {
576
+ this.controllerMode = 2;
577
+ this.person?.attach(this.vehicle);
578
+ }
527
579
  /**
528
580
  * 设置指针锁定
529
581
  */
@@ -596,6 +648,61 @@ var PlayerController = class {
596
648
  }
597
649
  }
598
650
  // ==================== 物理与碰撞检测 ====================
651
+ /**
652
+ * 统一属性集合
653
+ */
654
+ unifiedAttribute(collected) {
655
+ const attrMap = /* @__PURE__ */ new Map();
656
+ const attrConflict = /* @__PURE__ */ new Set();
657
+ const requiredAttrs = /* @__PURE__ */ new Set(["position", "normal", "uv"]);
658
+ for (const g of collected) {
659
+ const attrNames2 = Object.keys(g.attributes);
660
+ for (const name of attrNames2) {
661
+ if (!requiredAttrs.has(name)) {
662
+ g.deleteAttribute(name);
663
+ }
664
+ }
665
+ }
666
+ for (const g of collected) {
667
+ for (const name of Object.keys(g.attributes)) {
668
+ const attr = g.attributes[name];
669
+ const ctor = attr.array.constructor;
670
+ const itemSize = attr.itemSize;
671
+ const normalized = attr.normalized;
672
+ if (!attrMap.has(name)) {
673
+ attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1, normalized });
674
+ } else {
675
+ const m = attrMap.get(name);
676
+ if (m.itemSize !== itemSize || m.arrayCtor !== ctor || m.normalized !== normalized) {
677
+ attrConflict.add(name);
678
+ } else {
679
+ m.examples++;
680
+ }
681
+ }
682
+ }
683
+ }
684
+ if (attrConflict.size) {
685
+ for (const g of collected) {
686
+ for (const name of Array.from(attrConflict)) {
687
+ if (g.attributes[name]) g.deleteAttribute(name);
688
+ }
689
+ }
690
+ for (const name of attrConflict) attrMap.delete(name);
691
+ }
692
+ const attrNames = Array.from(attrMap.keys());
693
+ for (const g of collected) {
694
+ const count = g.attributes.position.count;
695
+ for (const name of attrNames) {
696
+ if (!g.attributes[name]) {
697
+ const meta = attrMap.get(name);
698
+ const len = count * meta.itemSize;
699
+ const array = new meta.arrayCtor(len);
700
+ g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize, meta.normalized));
701
+ }
702
+ }
703
+ }
704
+ return collected;
705
+ }
599
706
  /**
600
707
  * BVH碰撞体构建
601
708
  */
@@ -611,7 +718,7 @@ var PlayerController = class {
611
718
  }
612
719
  return geom;
613
720
  };
614
- const collected = [];
721
+ let collected = [];
615
722
  if (meshUrl === "") {
616
723
  if (this.collider) {
617
724
  this.scene.remove(this.collider);
@@ -632,45 +739,7 @@ var PlayerController = class {
632
739
  }
633
740
  });
634
741
  if (!collected.length) return;
635
- const attrMap = /* @__PURE__ */ new Map();
636
- const attrConflict = /* @__PURE__ */ new Set();
637
- for (const g of collected) {
638
- for (const name of Object.keys(g.attributes)) {
639
- const attr = g.attributes[name];
640
- const ctor = attr.array.constructor;
641
- const itemSize = attr.itemSize;
642
- if (!attrMap.has(name)) {
643
- attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1 });
644
- } else {
645
- const m = attrMap.get(name);
646
- if (m.itemSize !== itemSize || m.arrayCtor !== ctor) {
647
- attrConflict.add(name);
648
- } else {
649
- m.examples++;
650
- }
651
- }
652
- }
653
- }
654
- if (attrConflict.size) {
655
- for (const g of collected) {
656
- for (const name of Array.from(attrConflict)) {
657
- if (g.attributes[name]) g.deleteAttribute(name);
658
- }
659
- }
660
- for (const name of attrConflict) attrMap.delete(name);
661
- }
662
- const attrNames = Array.from(attrMap.keys());
663
- for (const g of collected) {
664
- const count = g.attributes.position.count;
665
- for (const name of attrNames) {
666
- if (!g.attributes[name]) {
667
- const meta = attrMap.get(name);
668
- const len = count * meta.itemSize;
669
- const array = new meta.arrayCtor(len);
670
- g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize));
671
- }
672
- }
673
- }
742
+ collected = this.unifiedAttribute(collected);
674
743
  } else {
675
744
  const gltf = await this.loader.loadAsync(meshUrl);
676
745
  const mesh = gltf.scene.children[0];
@@ -766,8 +835,12 @@ var PlayerController = class {
766
835
  this.player.position.addScaledVector(this.playerVelocity, delta);
767
836
  this.playerIsOnGround = true;
768
837
  } else {
769
- this.playerVelocity.set(0, 0, 0);
770
- this.playerIsOnGround = true;
838
+ if (this.spacePressed) {
839
+ this.playerVelocity.y += delta * this.gravity;
840
+ } else {
841
+ this.playerVelocity.set(0, 0, 0);
842
+ this.playerIsOnGround = true;
843
+ }
771
844
  }
772
845
  } else if (playerDistanceFromGround > minH && playerDistanceFromGround < h) {
773
846
  this.playerVelocity.set(0, 0, 0);
@@ -898,6 +971,7 @@ var PlayerController = class {
898
971
  */
899
972
  updateMixers(delta) {
900
973
  if (this.personMixer) this.personMixer.update(delta);
974
+ if (this.vehicleMixer) this.vehicleMixer.update(delta);
901
975
  }
902
976
  /**
903
977
  * 重置玩家位置
@@ -1220,7 +1294,8 @@ function playerController() {
1220
1294
  update: (dt) => c.update(dt),
1221
1295
  destroy: () => c.destroy(),
1222
1296
  setInput: (i) => c.setInput(i),
1223
- getposition: () => c.getPosition()
1297
+ getposition: () => c.getPosition(),
1298
+ loadVehicleModel: (params) => c.loadVehicleModel(params)
1224
1299
  };
1225
1300
  }
1226
1301
  function onAllEvent() {