three-player-controller 0.2.51 → 0.3.0

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/dist/index.mjs CHANGED
@@ -20,27 +20,28 @@ THREE.Mesh.prototype.raycast = acceleratedRaycast;
20
20
  var controllerInstance = null;
21
21
  var clock = new THREE.Clock();
22
22
  var PlayerController = class {
23
- // 射线检测时只返回第一个碰撞
24
23
  constructor() {
24
+ // ==================== 基本配置与参数 ====================
25
25
  this.loader = new GLTFLoader();
26
26
  // 0: 隐藏鼠标控制朝向及视角,1: 隐藏鼠标仅控制视角,2: 显示鼠标拖拽控制朝向及视角, 3: 显示鼠标拖拽仅控制视角
27
+ // ==================== 玩家基本属性 ====================
27
28
  this.playerRadius = 45;
28
29
  this.playerHeight = 180;
29
30
  this.isFirstPerson = false;
30
31
  this.boundingBoxMinY = 0;
31
- // 测试参数
32
+ // ==================== 测试参数 ====================
32
33
  this.displayPlayer = false;
33
34
  this.displayCollider = false;
34
35
  this.displayVisualizer = false;
35
- // 场景对象
36
+ // ==================== 场景对象 ====================
36
37
  this.collider = null;
37
38
  this.visualizer = null;
38
39
  this.person = null;
39
- // 状态开关
40
+ // ==================== 状态开关 ====================
40
41
  this.playerIsOnGround = false;
41
42
  this.isupdate = true;
42
43
  this.isFlying = false;
43
- // 输入状态
44
+ // ==================== 输入状态 ====================
44
45
  this.fwdPressed = false;
45
46
  this.bkdPressed = false;
46
47
  this.lftPressed = false;
@@ -48,10 +49,9 @@ var PlayerController = class {
48
49
  this.spacePressed = false;
49
50
  this.ctPressed = false;
50
51
  this.shiftPressed = false;
51
- // 移动端输入
52
+ // ==================== 移动端输入 ====================
52
53
  this.prevJoyState = { dirX: 0, dirY: 0, shift: false };
53
54
  this.nippleModule = null;
54
- // 移动控制相关
55
55
  this.joystickManager = null;
56
56
  this.joystickZoneEl = null;
57
57
  this.lookAreaEl = null;
@@ -62,29 +62,28 @@ var PlayerController = class {
62
62
  this.isLookDown = false;
63
63
  this.lastTouchX = 0;
64
64
  this.lastTouchY = 0;
65
- // 第三人称
65
+ // ==================== 第三人称相机参数 ====================
66
66
  this._camCollisionLerp = 0.18;
67
67
  // 平滑系数
68
68
  this._camEpsilon = 0.35;
69
- // 摄像机与障碍物之间的安全距离(米)
69
+ // 摄像机与障碍物之间的安全距离
70
70
  this._minCamDistance = 1;
71
71
  // 摄像机最小距离
72
72
  this._maxCamDistance = 4.4;
73
73
  // 摄像机最大距离
74
74
  this.orginMaxCamDistance = 4.4;
75
- // 物理/运动
75
+ // ==================== 物理/运动 ====================
76
76
  this.playerVelocity = new THREE.Vector3();
77
77
  // 玩家速度向量
78
78
  this.upVector = new THREE.Vector3(0, 1, 0);
79
- // 临时复用向量/矩阵
79
+ // ==================== 临时复用向量/矩阵 ====================
80
80
  this.tempVector = new THREE.Vector3();
81
81
  this.tempVector2 = new THREE.Vector3();
82
82
  this.tempBox = new THREE.Box3();
83
83
  this.tempMat = new THREE.Matrix4();
84
84
  this.tempSegment = new THREE.Line3();
85
- // 检测动画定时
86
85
  this.recheckAnimTimer = null;
87
- // 复用向量:用于相机朝向 / 移动
86
+ // ==================== 相机朝向/移动复用向量 ====================
88
87
  this.camDir = new THREE.Vector3();
89
88
  this.moveDir = new THREE.Vector3();
90
89
  this.targetQuat = new THREE.Quaternion();
@@ -95,13 +94,65 @@ var PlayerController = class {
95
94
  this.DIR_LFT = new THREE.Vector3(-1, 0, 0);
96
95
  this.DIR_RGT = new THREE.Vector3(1, 0, 0);
97
96
  this.DIR_UP = new THREE.Vector3(0, 1, 0);
97
+ // ==================== 射线检测 ====================
98
98
  this._personToCam = new THREE.Vector3();
99
99
  this._originTmp = new THREE.Vector3();
100
100
  this._raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0));
101
101
  this._raycasterPersonToCam = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3());
102
- // 键盘按下事件
102
+ /**
103
+ * 根据按键设置人物动画
104
+ */
105
+ this.setAnimationByPressed = () => {
106
+ this._maxCamDistance = this.orginMaxCamDistance;
107
+ if (this.isFlying) {
108
+ if (!this.fwdPressed) {
109
+ this.playPersonAnimationByName("flyidle");
110
+ return;
111
+ }
112
+ this.playPersonAnimationByName("flying");
113
+ this._maxCamDistance = this.orginMaxCamDistance * 2;
114
+ return;
115
+ }
116
+ if (this.playerIsOnGround) {
117
+ if (!this.fwdPressed && !this.bkdPressed && !this.lftPressed && !this.rgtPressed) {
118
+ this.playPersonAnimationByName("idle");
119
+ return;
120
+ }
121
+ if (this.fwdPressed) {
122
+ this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
123
+ return;
124
+ }
125
+ if (!this.isFirstPerson && (this.lftPressed || this.rgtPressed || this.bkdPressed)) {
126
+ this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
127
+ return;
128
+ }
129
+ if (this.lftPressed) {
130
+ this.playPersonAnimationByName("left_walking");
131
+ return;
132
+ }
133
+ if (this.rgtPressed) {
134
+ this.playPersonAnimationByName("right_walking");
135
+ return;
136
+ }
137
+ if (this.bkdPressed) {
138
+ this.playPersonAnimationByName("walking_backward");
139
+ return;
140
+ }
141
+ }
142
+ if (this.recheckAnimTimer !== null) {
143
+ clearTimeout(this.recheckAnimTimer);
144
+ }
145
+ this.recheckAnimTimer = setTimeout(() => {
146
+ this.setAnimationByPressed();
147
+ this.recheckAnimTimer = null;
148
+ }, 200);
149
+ };
150
+ // ==================== 事件处理 ====================
151
+ /**
152
+ * 键盘按下事件
153
+ */
103
154
  this._boundOnKeydown = async (e) => {
104
- if (e.ctrlKey && (e.code === "KeyW" || e.code === "KeyA" || e.code === "KeyS" || e.code === "KeyD")) {
155
+ if (e.ctrlKey && ["KeyW", "KeyA", "KeyS", "KeyD"].includes(e.code)) {
105
156
  e.preventDefault();
106
157
  }
107
158
  switch (e.code) {
@@ -124,11 +175,7 @@ var PlayerController = class {
124
175
  case "ShiftLeft":
125
176
  this.shiftPressed = true;
126
177
  this.setAnimationByPressed();
127
- this.controls.mouseButtons = {
128
- LEFT: 2,
129
- MIDDLE: 1,
130
- RIGHT: 0
131
- };
178
+ this.controls.mouseButtons = { LEFT: 2, MIDDLE: 1, RIGHT: 0 };
132
179
  break;
133
180
  case "Space":
134
181
  this.spacePressed = true;
@@ -148,11 +195,15 @@ var PlayerController = class {
148
195
  case "KeyF":
149
196
  this.isFlying = !this.isFlying;
150
197
  this.setAnimationByPressed();
151
- if (!this.isFlying && !this.playerIsOnGround) this.playPersonAnimationByName("jumping");
198
+ if (!this.isFlying && !this.playerIsOnGround) {
199
+ this.playPersonAnimationByName("jumping");
200
+ }
152
201
  break;
153
202
  }
154
203
  };
155
- // 键盘抬起事件
204
+ /**
205
+ * 键盘抬起事件
206
+ */
156
207
  this._boundOnKeyup = (e) => {
157
208
  switch (e.code) {
158
209
  case "KeyW":
@@ -174,11 +225,7 @@ var PlayerController = class {
174
225
  case "ShiftLeft":
175
226
  this.shiftPressed = false;
176
227
  this.setAnimationByPressed();
177
- this.controls.mouseButtons = {
178
- LEFT: 0,
179
- MIDDLE: 1,
180
- RIGHT: 2
181
- };
228
+ this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
182
229
  break;
183
230
  case "Space":
184
231
  this.spacePressed = false;
@@ -188,127 +235,85 @@ var PlayerController = class {
188
235
  break;
189
236
  }
190
237
  };
191
- // 根据按键设置人物动画
192
- this.setAnimationByPressed = () => {
193
- this._maxCamDistance = this.orginMaxCamDistance;
194
- if (this.isFlying) {
195
- if (!this.fwdPressed) {
196
- this.playPersonAnimationByName("flyidle");
197
- return;
198
- }
199
- this.playPersonAnimationByName("flying");
200
- this._maxCamDistance = this.orginMaxCamDistance * 2;
201
- return;
202
- }
203
- if (this.playerIsOnGround) {
204
- if (!this.fwdPressed && !this.bkdPressed && !this.lftPressed && !this.rgtPressed) {
205
- this.playPersonAnimationByName("idle");
206
- return;
207
- }
208
- if (this.fwdPressed) {
209
- if (this.shiftPressed) {
210
- this.playPersonAnimationByName("running");
211
- } else {
212
- this.playPersonAnimationByName("walking");
213
- }
214
- return;
215
- }
216
- if (!this.isFirstPerson && (this.lftPressed || this.rgtPressed || this.bkdPressed)) {
217
- if (this.shiftPressed) {
218
- this.playPersonAnimationByName("running");
219
- } else {
220
- this.playPersonAnimationByName("walking");
221
- }
222
- return;
223
- }
224
- if (this.lftPressed) {
225
- this.playPersonAnimationByName("left_walking");
226
- return;
227
- }
228
- if (this.rgtPressed) {
229
- this.playPersonAnimationByName("right_walking");
230
- return;
231
- }
232
- if (this.bkdPressed) {
233
- this.playPersonAnimationByName("walking_backward");
234
- return;
235
- }
236
- }
237
- if (this.recheckAnimTimer !== null) {
238
- clearTimeout(this.recheckAnimTimer);
239
- }
240
- this.recheckAnimTimer = setTimeout(() => {
241
- this.setAnimationByPressed();
242
- this.recheckAnimTimer = null;
243
- }, 200);
244
- };
245
- // 鼠标移动事件
238
+ /**
239
+ * 鼠标移动事件
240
+ */
246
241
  this._mouseMove = (e) => {
247
242
  if (document.pointerLockElement !== document.body) return;
248
243
  this.setToward(e.movementX, e.movementY, 1e-4);
249
244
  };
245
+ /**
246
+ * 鼠标点击事件
247
+ */
250
248
  this._mouseClick = (e) => {
251
249
  this.setPointerLock();
252
250
  };
251
+ // ==================== 移动端控制 ====================
252
+ /**
253
+ * 指针按下事件
254
+ */
253
255
  this.onPointerDown = (e) => {
254
256
  if (e.pointerType !== "touch") return;
255
257
  this.isLookDown = true;
256
258
  this.lookPointerId = e.pointerId;
257
259
  this.lastTouchX = e.clientX;
258
260
  this.lastTouchY = e.clientY;
259
- this.lookAreaEl?.setPointerCapture && this.lookAreaEl.setPointerCapture(e.pointerId);
261
+ this.lookAreaEl?.setPointerCapture?.(e.pointerId);
260
262
  e.preventDefault();
261
263
  };
264
+ /**
265
+ * 指针移动事件
266
+ */
262
267
  this.onPointerMove = (e) => {
263
268
  if (!this.isLookDown || e.pointerId !== this.lookPointerId) return;
264
269
  const dx = e.clientX - this.lastTouchX;
265
270
  const dy = e.clientY - this.lastTouchY;
266
271
  this.lastTouchX = e.clientX;
267
272
  this.lastTouchY = e.clientY;
268
- this.setInput({
269
- lookDeltaX: dx,
270
- lookDeltaY: dy
271
- });
273
+ this.setInput({ lookDeltaX: dx, lookDeltaY: dy });
272
274
  e.preventDefault();
273
275
  };
276
+ /**
277
+ * 指针抬起事件
278
+ */
274
279
  this.onPointerUp = (e) => {
275
280
  if (e.pointerId !== this.lookPointerId) return;
276
281
  this.isLookDown = false;
277
282
  this.lookPointerId = null;
278
- this.lookAreaEl?.releasePointerCapture && this.lookAreaEl.releasePointerCapture(e.pointerId);
283
+ this.lookAreaEl?.releasePointerCapture?.(e.pointerId);
279
284
  };
280
285
  this._raycaster.firstHitOnly = true;
281
286
  this._raycasterPersonToCam.firstHitOnly = true;
282
287
  }
283
- // 初始化
288
+ // ==================== 初始化相关方法 ====================
289
+ /**
290
+ * 初始化控制器
291
+ */
284
292
  async init(opts, callback) {
285
293
  this.scene = opts.scene;
286
294
  this.camera = opts.camera;
287
295
  this.camera.rotation.order = "YXZ";
288
296
  this.controls = opts.controls;
289
297
  this.playerModel = opts.playerModel;
290
- this.initPos = opts.initPos ? opts.initPos : new THREE.Vector3(0, 0, 0);
291
- this.intDirection = opts.intDirection ? opts.intDirection : new THREE.Vector3(0, 0, -1);
292
- this.mouseSensity = opts.mouseSensity ? opts.mouseSensity : 5;
298
+ this.initPos = opts.initPos ?? new THREE.Vector3(0, 0, 0);
299
+ this.mouseSensity = opts.mouseSensity ?? 5;
293
300
  const s = this.playerModel.scale;
294
301
  this.visualizeDepth = 0 * s;
295
- this.gravity = opts.playerModel.gravity ? opts.playerModel.gravity * s : -2400 * s;
296
- this.jumpHeight = opts.playerModel.jumpHeight ? opts.playerModel.jumpHeight * s : 800 * s;
297
- this.originPlayerSpeed = opts.playerModel.speed ? opts.playerModel.speed * s : 400 * s;
302
+ this.gravity = (opts.playerModel.gravity ?? -2400) * s;
303
+ this.jumpHeight = (opts.playerModel.jumpHeight ?? 800) * s;
304
+ this.originPlayerSpeed = (opts.playerModel.speed ?? 400) * s;
298
305
  this.playerSpeed = this.originPlayerSpeed;
299
- this.playerModel.rotateY = opts.playerModel.rotateY ? opts.playerModel.rotateY : 0;
306
+ this.playerModel.rotateY = opts.playerModel.rotateY ?? 0;
300
307
  this._camCollisionLerp = 0.18;
301
308
  this._camEpsilon = 35 * s;
302
- this._minCamDistance = opts.minCamDistance ? opts.minCamDistance * s : 100 * s;
303
- this._maxCamDistance = opts.maxCamDistance ? opts.maxCamDistance * s : 440 * s;
309
+ this._minCamDistance = (opts.minCamDistance ?? 100) * s;
310
+ this._maxCamDistance = (opts.maxCamDistance ?? 440) * s;
304
311
  this.orginMaxCamDistance = this._maxCamDistance;
305
312
  this.thirdMouseMode = opts.thirdMouseMode ?? 1;
306
- function isMobileDevice() {
307
- return navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
308
- }
313
+ const isMobileDevice = () => navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || "ontouchstart" in window || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
309
314
  this.isShowMobileControls = (opts.isShowMobileControls ?? true) && isMobileDevice();
310
315
  if (this.isShowMobileControls) {
311
- this.initMobileControls();
316
+ await this.initMobileControls();
312
317
  }
313
318
  await this.createBVH(opts.colliderMeshUrl);
314
319
  this.createPlayer();
@@ -321,73 +326,19 @@ var PlayerController = class {
321
326
  this.setControls();
322
327
  if (callback) callback();
323
328
  }
324
- // 第一/三视角切换
325
- changeView() {
326
- this.isFirstPerson = !this.isFirstPerson;
327
- if (this.isFirstPerson) {
328
- this.player.attach(this.camera);
329
- this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
330
- this.camera.rotation.set(0, Math.PI, 0);
331
- this.setPointerLock();
332
- } else {
333
- this.scene.attach(this.camera);
334
- const worldPos = this.player.position.clone();
335
- const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
336
- const angle = Math.atan2(dir.z, dir.x);
337
- const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, 200 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
338
- this.camera.position.copy(worldPos).add(offset);
339
- this.controls.target.copy(worldPos);
340
- this.setPointerLock();
341
- }
342
- }
343
- setPointerLock() {
344
- if ((this.thirdMouseMode == 0 || this.thirdMouseMode == 1) && !this.isFirstPerson || this.isFirstPerson) document.body.requestPointerLock();
345
- }
346
- // 摄像机/控制器设置
347
- setCameraPos() {
348
- if (this.isFirstPerson) {
349
- this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
350
- } else {
351
- const worldPos = this.player.position.clone();
352
- const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
353
- const angle = Math.atan2(dir.z, dir.x);
354
- const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, -100 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
355
- this.camera.position.copy(worldPos).add(offset);
356
- }
357
- this.camera.updateProjectionMatrix();
358
- }
359
- // 设置控制器
360
- setControls() {
361
- if (this.thirdMouseMode == 0 || this.thirdMouseMode == 1) {
362
- this.controls.enabled = false;
363
- } else {
364
- this.controls.enabled = true;
365
- }
366
- this.controls.rotateSpeed = this.mouseSensity * 0.05;
367
- this.controls.maxPolarAngle = Math.PI * (300 / 360);
368
- }
369
- // 重置控制器
370
- resetControls() {
371
- if (!this.controls) return;
372
- this.controls.enabled = true;
373
- this.controls.enablePan = true;
374
- this.controls.maxPolarAngle = Math.PI / 2;
375
- this.controls.rotateSpeed = 1;
376
- this.controls.enableZoom = true;
377
- this.controls.mouseButtons = {
378
- LEFT: 0,
379
- MIDDLE: 1,
380
- RIGHT: 2
381
- };
382
- }
383
- // 初始化加载器
329
+ /**
330
+ * 初始化加载器
331
+ */
384
332
  async initLoader() {
385
333
  const dracoLoader = new DRACOLoader();
386
334
  dracoLoader.setDecoderPath("https://unpkg.com/three@0.180.0/examples/jsm/libs/draco/gltf/");
387
335
  dracoLoader.setDecoderConfig({ type: "js" });
388
336
  this.loader.setDRACOLoader(dracoLoader);
389
337
  }
390
- // 人物与动画加载
338
+ // ==================== 玩家模型相关方法 ====================
339
+ /**
340
+ * 加载玩家模型与动画
341
+ */
391
342
  async loadPersonGLB() {
392
343
  try {
393
344
  const gltf = await this.loader.loadAsync(this.playerModel.url);
@@ -407,8 +358,7 @@ var PlayerController = class {
407
358
  this.personMixer = new THREE.AnimationMixer(this.person);
408
359
  const animations = gltf.animations ?? [];
409
360
  this.personActions = /* @__PURE__ */ new Map();
410
- const findClip = (name) => animations.find((a) => a.name === name);
411
- const regs = [
361
+ const animationMappings = [
412
362
  [this.playerModel.idleAnim, "idle"],
413
363
  [this.playerModel.walkAnim, "walking"],
414
364
  [this.playerModel.leftWalkAnim || this.playerModel.walkAnim, "left_walking"],
@@ -419,11 +369,12 @@ var PlayerController = class {
419
369
  [this.playerModel.flyIdleAnim || this.playerModel.idleAnim, "flyidle"],
420
370
  [this.playerModel.flyAnim || this.playerModel.idleAnim, "flying"]
421
371
  ];
422
- for (const [key, clipName] of regs) {
423
- const clip = findClip(key);
372
+ const findClip = (name) => animations.find((a) => a.name === name);
373
+ for (const [clipName, actionName] of animationMappings) {
374
+ const clip = findClip(clipName);
424
375
  if (!clip) continue;
425
376
  const action = this.personMixer.clipAction(clip);
426
- if (clipName === "jumping") {
377
+ if (actionName === "jumping") {
427
378
  action.setLoop(THREE.LoopOnce, 1);
428
379
  action.clampWhenFinished = true;
429
380
  action.setEffectiveTimeScale(1.2);
@@ -434,7 +385,7 @@ var PlayerController = class {
434
385
  }
435
386
  action.enabled = true;
436
387
  action.setEffectiveWeight(0);
437
- this.personActions.set(clipName, action);
388
+ this.personActions.set(actionName, action);
438
389
  }
439
390
  this.idleAction = this.personActions.get("idle");
440
391
  this.walkAction = this.personActions.get("walking");
@@ -452,8 +403,7 @@ var PlayerController = class {
452
403
  const finishedAction = ev.action;
453
404
  if (finishedAction === this.jumpAction) {
454
405
  if (this.fwdPressed) {
455
- if (this.shiftPressed) this.playPersonAnimationByName("running");
456
- else this.playPersonAnimationByName("walking");
406
+ this.playPersonAnimationByName(this.shiftPressed ? "running" : "walking");
457
407
  return;
458
408
  }
459
409
  if (this.bkdPressed) {
@@ -468,15 +418,16 @@ var PlayerController = class {
468
418
  }
469
419
  });
470
420
  } catch (error) {
421
+ console.error("\u52A0\u8F7D\u73A9\u5BB6\u6A21\u578B\u5931\u8D25:", error);
471
422
  }
472
423
  }
473
- // 平滑切换人物动画
424
+ /**
425
+ * 平滑切换人物动画
426
+ */
474
427
  playPersonAnimationByName(name, fade = 0.18) {
475
- if (!this.personActions) return;
476
- if (this.ctPressed) return;
428
+ if (!this.personActions || this.ctPressed) return;
477
429
  const next = this.personActions.get(name);
478
- if (!next) return;
479
- if (this.actionState === next) return;
430
+ if (!next || this.actionState === next) return;
480
431
  const prev = this.actionState;
481
432
  next.reset();
482
433
  next.setEffectiveWeight(1);
@@ -487,46 +438,254 @@ var PlayerController = class {
487
438
  } else {
488
439
  next.fadeIn(fade);
489
440
  }
490
- this.actionState = next;
491
- }
492
- // 创建玩家胶囊体
493
- createPlayer() {
494
- const material = new THREE.MeshStandardMaterial({
495
- color: new THREE.Color(1, 0, 0),
496
- shadowSide: THREE.DoubleSide,
497
- depthTest: false
498
- });
499
- material.transparent = true;
500
- material.opacity = this.displayPlayer ? 0.5 : 0;
501
- material.wireframe = true;
502
- material.depthWrite = false;
503
- const r = this.playerRadius * this.playerModel.scale;
504
- const h = this.playerHeight * this.playerModel.scale;
505
- this.player = new THREE.Mesh(new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75), material);
506
- this.player.geometry.translate(0, -h * 0.25, 0);
507
- this.player.capsuleInfo = {
508
- radius: r,
509
- segment: new THREE.Line3(new THREE.Vector3(), new THREE.Vector3(0, -h * 0.5, 0))
510
- };
511
- this.player.name = "capsule";
512
- this.scene.add(this.player);
513
- this.reset();
514
- this.player.rotateY(this.playerModel.rotateY ?? 0);
441
+ this.actionState = next;
442
+ }
443
+ /**
444
+ * 创建玩家胶囊体
445
+ */
446
+ createPlayer() {
447
+ const material = new THREE.MeshStandardMaterial({
448
+ color: new THREE.Color(1, 0, 0),
449
+ shadowSide: THREE.DoubleSide,
450
+ depthTest: false,
451
+ transparent: true,
452
+ opacity: this.displayPlayer ? 0.5 : 0,
453
+ wireframe: true,
454
+ depthWrite: false
455
+ });
456
+ const r = this.playerRadius * this.playerModel.scale;
457
+ const h = this.playerHeight * this.playerModel.scale;
458
+ this.player = new THREE.Mesh(new RoundedBoxGeometry(r * 2, h, r * 2, 1, 75), material);
459
+ this.player.geometry.translate(0, -h * 0.25, 0);
460
+ this.player.capsuleInfo = {
461
+ radius: r,
462
+ segment: new THREE.Line3(new THREE.Vector3(), new THREE.Vector3(0, -h * 0.5, 0))
463
+ };
464
+ this.player.name = "capsule";
465
+ this.scene.add(this.player);
466
+ this.reset();
467
+ this.player.rotateY(this.playerModel.rotateY ?? 0);
468
+ }
469
+ // ==================== 相机与视角控制 ====================
470
+ /**
471
+ * 第一/三人称视角切换
472
+ */
473
+ changeView() {
474
+ this.isFirstPerson = !this.isFirstPerson;
475
+ if (this.isFirstPerson) {
476
+ this.player.attach(this.camera);
477
+ this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
478
+ this.camera.rotation.set(0, Math.PI, 0);
479
+ } else {
480
+ this.scene.attach(this.camera);
481
+ const worldPos = this.player.position.clone();
482
+ const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
483
+ const angle = Math.atan2(dir.z, dir.x);
484
+ const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, 200 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
485
+ this.camera.position.copy(worldPos).add(offset);
486
+ this.controls.target.copy(worldPos);
487
+ }
488
+ this.setPointerLock();
489
+ }
490
+ /**
491
+ * 设置指针锁定
492
+ */
493
+ setPointerLock() {
494
+ if ((this.thirdMouseMode === 0 || this.thirdMouseMode === 1) && !this.isFirstPerson || this.isFirstPerson) {
495
+ document.body.requestPointerLock();
496
+ }
497
+ }
498
+ /**
499
+ * 设置摄像机初始位置
500
+ */
501
+ setCameraPos() {
502
+ if (this.isFirstPerson) {
503
+ this.camera.position.set(0, 40 * this.playerModel.scale, 30 * this.playerModel.scale);
504
+ } else {
505
+ const worldPos = this.player.position.clone();
506
+ const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(this.player.quaternion);
507
+ const angle = Math.atan2(dir.z, dir.x);
508
+ const offset = new THREE.Vector3(Math.cos(angle) * 400 * this.playerModel.scale, -100 * this.playerModel.scale, Math.sin(angle) * 400 * this.playerModel.scale);
509
+ this.camera.position.copy(worldPos).add(offset);
510
+ }
511
+ this.camera.updateProjectionMatrix();
512
+ }
513
+ /**
514
+ * 设置控制器
515
+ */
516
+ setControls() {
517
+ this.controls.enabled = !(this.thirdMouseMode === 0 || this.thirdMouseMode === 1);
518
+ this.controls.rotateSpeed = this.mouseSensity * 0.05;
519
+ this.controls.maxPolarAngle = Math.PI * (300 / 360);
520
+ }
521
+ /**
522
+ * 重置控制器
523
+ */
524
+ resetControls() {
525
+ if (!this.controls) return;
526
+ this.controls.enabled = true;
527
+ this.controls.enablePan = true;
528
+ this.controls.maxPolarAngle = Math.PI / 2;
529
+ this.controls.rotateSpeed = 1;
530
+ this.controls.enableZoom = true;
531
+ this.controls.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };
532
+ }
533
+ /**
534
+ * 设置朝向
535
+ */
536
+ setToward(dx, dy, speed) {
537
+ if (this.isFirstPerson) {
538
+ const yaw = -dx * speed * this.mouseSensity;
539
+ const pitch = -dy * speed * this.mouseSensity;
540
+ this.player.rotateY(yaw);
541
+ this.camera.rotation.x = THREE.MathUtils.clamp(this.camera.rotation.x + pitch, -1.1, 1.4);
542
+ } else {
543
+ const sensitivity = this.mouseSensity;
544
+ const deltaX = -dx * speed * sensitivity;
545
+ const deltaY = -dy * speed * sensitivity;
546
+ const target = this.player.position.clone();
547
+ const distance = this.camera.position.distanceTo(target);
548
+ const currentPosition = this.camera.position.clone().sub(target);
549
+ let theta = Math.atan2(currentPosition.x, currentPosition.z);
550
+ let phi = Math.acos(currentPosition.y / distance);
551
+ theta += deltaX;
552
+ phi += deltaY;
553
+ phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
554
+ const newX = distance * Math.sin(phi) * Math.sin(theta);
555
+ const newY = distance * Math.cos(phi);
556
+ const newZ = distance * Math.sin(phi) * Math.cos(theta);
557
+ this.camera.position.set(target.x + newX, target.y + newY, target.z + newZ);
558
+ this.camera.lookAt(target);
559
+ }
560
+ }
561
+ // ==================== 物理与碰撞检测 ====================
562
+ /**
563
+ * BVH碰撞体构建
564
+ */
565
+ async createBVH(meshUrl = "") {
566
+ await this.initLoader();
567
+ const ensureAttributesMinimal = (geom) => {
568
+ if (!geom.attributes.position) return null;
569
+ if (!geom.attributes.normal) geom.computeVertexNormals();
570
+ if (!geom.attributes.uv) {
571
+ const count = geom.attributes.position.count;
572
+ const dummyUV = new Float32Array(count * 2);
573
+ geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
574
+ }
575
+ return geom;
576
+ };
577
+ const collected = [];
578
+ if (meshUrl === "") {
579
+ if (this.collider) {
580
+ this.scene.remove(this.collider);
581
+ this.collider = null;
582
+ }
583
+ this.scene.traverse((c) => {
584
+ const mesh = c;
585
+ if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
586
+ try {
587
+ let geom = mesh.geometry.clone();
588
+ geom.applyMatrix4(mesh.matrixWorld);
589
+ if (geom.index) geom = geom.toNonIndexed();
590
+ const safe = ensureAttributesMinimal(geom);
591
+ if (safe) collected.push(safe);
592
+ } catch (e) {
593
+ console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
594
+ }
595
+ }
596
+ });
597
+ if (!collected.length) return;
598
+ const attrMap = /* @__PURE__ */ new Map();
599
+ const attrConflict = /* @__PURE__ */ new Set();
600
+ for (const g of collected) {
601
+ for (const name of Object.keys(g.attributes)) {
602
+ const attr = g.attributes[name];
603
+ const ctor = attr.array.constructor;
604
+ const itemSize = attr.itemSize;
605
+ if (!attrMap.has(name)) {
606
+ attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1 });
607
+ } else {
608
+ const m = attrMap.get(name);
609
+ if (m.itemSize !== itemSize || m.arrayCtor !== ctor) {
610
+ attrConflict.add(name);
611
+ } else {
612
+ m.examples++;
613
+ }
614
+ }
615
+ }
616
+ }
617
+ if (attrConflict.size) {
618
+ for (const g of collected) {
619
+ for (const name of Array.from(attrConflict)) {
620
+ if (g.attributes[name]) g.deleteAttribute(name);
621
+ }
622
+ }
623
+ for (const name of attrConflict) attrMap.delete(name);
624
+ }
625
+ const attrNames = Array.from(attrMap.keys());
626
+ for (const g of collected) {
627
+ const count = g.attributes.position.count;
628
+ for (const name of attrNames) {
629
+ if (!g.attributes[name]) {
630
+ const meta = attrMap.get(name);
631
+ const len = count * meta.itemSize;
632
+ const array = new meta.arrayCtor(len);
633
+ g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize));
634
+ }
635
+ }
636
+ }
637
+ } else {
638
+ const gltf = await this.loader.loadAsync(meshUrl);
639
+ const mesh = gltf.scene.children[0];
640
+ mesh.name = "BVH\u52A0\u8F7D\u6A21\u578B";
641
+ let geom = mesh.geometry.clone();
642
+ geom.applyMatrix4(mesh.matrixWorld);
643
+ if (geom.index) geom = geom.toNonIndexed();
644
+ const safe = ensureAttributesMinimal(geom);
645
+ if (safe) collected.push(safe);
646
+ }
647
+ const merged = BufferGeometryUtils.mergeGeometries(collected, false);
648
+ if (!merged) {
649
+ console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
650
+ return;
651
+ }
652
+ merged.boundsTree = new MeshBVH(merged, { maxDepth: 100 });
653
+ this.collider = new THREE.Mesh(
654
+ merged,
655
+ new THREE.MeshBasicMaterial({
656
+ opacity: 0.5,
657
+ transparent: true,
658
+ wireframe: true
659
+ })
660
+ );
661
+ if (this.displayCollider) this.scene.add(this.collider);
662
+ if (this.displayVisualizer) {
663
+ if (this.visualizer) this.scene.remove(this.visualizer);
664
+ this.visualizer = new MeshBVHHelper(this.collider, this.visualizeDepth);
665
+ this.scene.add(this.visualizer);
666
+ }
667
+ this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
515
668
  }
516
- // 获取法线与Y轴的夹角
669
+ /**
670
+ * 获取法线与Y轴的夹角
671
+ */
517
672
  getAngleWithYAxis(normal) {
518
673
  const yAxis = { x: 0, y: 1, z: 0 };
519
674
  const dotProduct = normal.x * yAxis.x + normal.y * yAxis.y + normal.z * yAxis.z;
520
675
  const normalMagnitude = Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
521
- const yAxisMagnitude = 1;
522
- const cosTheta = dotProduct / (normalMagnitude * yAxisMagnitude);
676
+ const cosTheta = dotProduct / normalMagnitude;
523
677
  return Math.acos(cosTheta);
524
678
  }
525
- // 每帧更新
679
+ // ==================== 循环更新 ====================
680
+ /**
681
+ * 每帧更新
682
+ */
526
683
  async update(delta = clock.getDelta()) {
527
684
  if (!this.isupdate || !this.player || !this.collider) return;
528
685
  delta = Math.min(delta, 1 / 30);
529
- if (!this.isFlying) this.player.position.addScaledVector(this.playerVelocity, delta);
686
+ if (!this.isFlying) {
687
+ this.player.position.addScaledVector(this.playerVelocity, delta);
688
+ }
530
689
  this.updateMixers(delta);
531
690
  this.camera.getWorldDirection(this.camDir);
532
691
  let angle = Math.atan2(this.camDir.z, this.camDir.x) + Math.PI / 2;
@@ -537,14 +696,9 @@ var PlayerController = class {
537
696
  if (this.lftPressed) this.moveDir.add(this.DIR_LFT);
538
697
  if (this.rgtPressed) this.moveDir.add(this.DIR_RGT);
539
698
  if (this.isFlying) {
540
- if (this.fwdPressed) {
541
- this.moveDir.y = this.camDir.y;
542
- } else {
543
- this.moveDir.y = 0;
544
- }
545
- if (this.spacePressed) {
546
- this.moveDir.add(this.DIR_UP);
547
- }
699
+ if (this.fwdPressed) this.moveDir.y = this.camDir.y;
700
+ else this.moveDir.y = 0;
701
+ if (this.spacePressed) this.moveDir.add(this.DIR_UP);
548
702
  }
549
703
  if (this.isFlying && this.fwdPressed) {
550
704
  this.playerSpeed = this.shiftPressed ? this.originPlayerSpeed * 12 : this.originPlayerSpeed * 7;
@@ -564,8 +718,7 @@ var PlayerController = class {
564
718
  const maxH = this.playerHeight * this.playerModel.scale * 0.9;
565
719
  const h = this.playerHeight * this.playerModel.scale * 0.75;
566
720
  const minH = this.playerHeight * this.playerModel.scale * 0.7;
567
- if (this.isFlying) {
568
- } else {
721
+ if (!this.isFlying) {
569
722
  if (playerDistanceFromGround > maxH) {
570
723
  this.playerVelocity.y += delta * this.gravity;
571
724
  this.player.position.addScaledVector(this.playerVelocity, delta);
@@ -628,7 +781,7 @@ var PlayerController = class {
628
781
  this.moveDir.normalize();
629
782
  this.moveDir.negate();
630
783
  let lookTarget;
631
- if (this.thirdMouseMode == 0 || this.thirdMouseMode == 2) {
784
+ if (this.thirdMouseMode === 0 || this.thirdMouseMode === 2) {
632
785
  if (this.moveDir.lengthSq() > 0) {
633
786
  lookTarget = this.player.position.clone().add(this.moveDir);
634
787
  } else {
@@ -639,7 +792,7 @@ var PlayerController = class {
639
792
  const alpha = Math.min(1, this.rotationSpeed * delta);
640
793
  this.player.quaternion.slerp(this.targetQuat, alpha);
641
794
  }
642
- if ((this.thirdMouseMode == 1 || this.thirdMouseMode == 3) && this.moveDir.lengthSq() > 0) {
795
+ if ((this.thirdMouseMode === 1 || this.thirdMouseMode === 3) && this.moveDir.lengthSq() > 0) {
643
796
  lookTarget = this.player.position.clone().add(this.moveDir);
644
797
  this.targetMat.lookAt(this.player.position, lookTarget, this.player.up);
645
798
  this.targetQuat.setFromRotationMatrix(this.targetMat);
@@ -703,206 +856,39 @@ var PlayerController = class {
703
856
  }
704
857
  }
705
858
  }
706
- // 重置
859
+ /**
860
+ * 更新模型动画
861
+ */
862
+ updateMixers(delta) {
863
+ if (this.personMixer) this.personMixer.update(delta);
864
+ }
865
+ /**
866
+ * 重置玩家位置
867
+ */
707
868
  reset(position) {
708
869
  if (!this.player) return;
709
870
  this.playerVelocity.set(0, 0, 0);
710
- this.player.position.copy(position ? position : this.initPos);
711
- }
712
- // 销毁
713
- destroy() {
714
- this.offAllEvent();
715
- if (this.player) {
716
- this.player.remove(this.camera);
717
- this.scene.remove(this.player);
718
- }
719
- this.player = null;
720
- if (this.person) {
721
- this.scene.remove(this.person);
722
- this.person = null;
723
- }
724
- this.resetControls();
725
- if (this.visualizer) {
726
- this.scene.remove(this.visualizer);
727
- this.visualizer = null;
728
- }
729
- if (this.collider) {
730
- this.scene.remove(this.collider);
731
- this.collider = null;
732
- }
733
- this.destroyMobileControls();
734
- controllerInstance = null;
735
- }
736
- // 事件绑定
737
- onAllEvent() {
738
- this.isupdate = true;
739
- this.setPointerLock();
740
- window.addEventListener("keydown", this._boundOnKeydown);
741
- window.addEventListener("keyup", this._boundOnKeyup);
742
- window.addEventListener("mousemove", this._mouseMove);
743
- window.addEventListener("click", this._mouseClick);
744
- }
745
- // 事件解绑
746
- offAllEvent() {
747
- this.isupdate = false;
748
- document.exitPointerLock();
749
- window.removeEventListener("keydown", this._boundOnKeydown);
750
- window.removeEventListener("keyup", this._boundOnKeyup);
751
- window.removeEventListener("mousemove", this._mouseMove);
752
- window.removeEventListener("click", this._mouseClick);
871
+ this.player.position.copy(position ?? this.initPos);
753
872
  }
873
+ /**
874
+ * 获取玩家位置
875
+ */
754
876
  getPosition() {
755
877
  return this.player.position;
756
878
  }
757
- // 更新模型动画
758
- updateMixers(delta) {
759
- if (this.personMixer) this.personMixer.update(delta);
760
- }
761
- // BVH构建
762
- async createBVH(meshUrl = "") {
763
- await this.initLoader();
764
- const ensureAttributesMinimal = (geom) => {
765
- if (!geom.attributes.position) {
766
- return null;
767
- }
768
- if (!geom.attributes.normal) geom.computeVertexNormals();
769
- if (!geom.attributes.uv) {
770
- const count = geom.attributes.position.count;
771
- const dummyUV = new Float32Array(count * 2);
772
- geom.setAttribute("uv", new THREE.BufferAttribute(dummyUV, 2));
773
- }
774
- return geom;
775
- };
776
- const collected = [];
777
- if (meshUrl == "") {
778
- if (this.collider) {
779
- this.scene.remove(this.collider);
780
- this.collider = null;
781
- }
782
- this.scene.traverse((c) => {
783
- const mesh = c;
784
- if (mesh?.isMesh && mesh.geometry && c.name !== "capsule") {
785
- try {
786
- let geom = mesh.geometry.clone();
787
- geom.applyMatrix4(mesh.matrixWorld);
788
- if (geom.index) geom = geom.toNonIndexed();
789
- const safe = ensureAttributesMinimal(geom);
790
- if (safe) collected.push(safe);
791
- } catch (e) {
792
- console.warn("\u5904\u7406\u7F51\u683C\u65F6\u51FA\u9519\uFF1A", mesh, e);
793
- }
794
- }
795
- });
796
- if (!collected.length) {
797
- return;
798
- }
799
- const attrMap = /* @__PURE__ */ new Map();
800
- const attrConflict = /* @__PURE__ */ new Set();
801
- for (const g of collected) {
802
- for (const name of Object.keys(g.attributes)) {
803
- const attr = g.attributes[name];
804
- const ctor = attr.array.constructor;
805
- const itemSize = attr.itemSize;
806
- if (!attrMap.has(name)) {
807
- attrMap.set(name, { itemSize, arrayCtor: ctor, examples: 1 });
808
- } else {
809
- const m = attrMap.get(name);
810
- if (m.itemSize !== itemSize || m.arrayCtor !== ctor) attrConflict.add(name);
811
- else m.examples++;
812
- }
813
- }
814
- }
815
- if (attrConflict.size) {
816
- for (const g of collected) {
817
- for (const name of Array.from(attrConflict)) {
818
- if (g.attributes[name]) g.deleteAttribute(name);
819
- }
820
- }
821
- for (const name of attrConflict) attrMap.delete(name);
822
- }
823
- const attrNames = Array.from(attrMap.keys());
824
- for (const g of collected) {
825
- const count = g.attributes.position.count;
826
- for (const name of attrNames) {
827
- if (!g.attributes[name]) {
828
- const meta = attrMap.get(name);
829
- const len = count * meta.itemSize;
830
- const array = new meta.arrayCtor(len);
831
- g.setAttribute(name, new THREE.BufferAttribute(array, meta.itemSize));
832
- }
833
- }
834
- }
835
- } else {
836
- const gltf = await this.loader.loadAsync(meshUrl, (xhr) => {
837
- });
838
- const mesh = gltf.scene.children[0];
839
- mesh.name = "BVH\u52A0\u8F7D\u6A21\u578B";
840
- let geom = mesh.geometry.clone();
841
- geom.applyMatrix4(mesh.matrixWorld);
842
- if (geom.index) geom = geom.toNonIndexed();
843
- const safe = ensureAttributesMinimal(geom);
844
- if (safe) collected.push(safe);
845
- }
846
- const merged = BufferGeometryUtils.mergeGeometries(collected, false);
847
- if (!merged) {
848
- console.error("\u5408\u5E76\u51E0\u4F55\u5931\u8D25");
849
- return;
850
- }
851
- merged.boundsTree = new MeshBVH(merged, {
852
- maxDepth: 100
853
- });
854
- this.collider = new THREE.Mesh(
855
- merged,
856
- new THREE.MeshBasicMaterial({
857
- opacity: 0.5,
858
- transparent: true,
859
- wireframe: true
860
- })
861
- );
862
- if (this.displayCollider) this.scene.add(this.collider);
863
- if (this.displayVisualizer) {
864
- if (this.visualizer) this.scene.remove(this.visualizer);
865
- this.visualizer = new MeshBVHHelper(this.collider, this.visualizeDepth);
866
- this.scene.add(this.visualizer);
867
- }
868
- this.boundingBoxMinY = this.collider.geometry.boundingBox.min.y;
869
- }
870
- // 设置朝向
871
- setToward(dx, dy, speed) {
872
- if (this.isFirstPerson) {
873
- const yaw = -dx * speed * this.mouseSensity;
874
- const pitch = -dy * speed * this.mouseSensity;
875
- this.player.rotateY(yaw);
876
- this.camera.rotation.x = THREE.MathUtils.clamp(this.camera.rotation.x + pitch, -1.1, 1.4);
877
- } else {
878
- const sensitivity = this.mouseSensity;
879
- const deltaX = -dx * speed * sensitivity;
880
- const deltaY = -dy * speed * sensitivity;
881
- const target = this.player.position.clone();
882
- const distance = this.camera.position.distanceTo(target);
883
- const currentPosition = this.camera.position.clone().sub(target);
884
- let theta = Math.atan2(currentPosition.x, currentPosition.z);
885
- let phi = Math.acos(currentPosition.y / distance);
886
- theta += deltaX;
887
- phi += deltaY;
888
- phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi));
889
- const newX = distance * Math.sin(phi) * Math.sin(theta);
890
- const newY = distance * Math.cos(phi);
891
- const newZ = distance * Math.sin(phi) * Math.cos(theta);
892
- this.camera.position.set(target.x + newX, target.y + newY, target.z + newZ);
893
- this.camera.lookAt(target);
894
- }
895
- }
896
- // 设置输入
879
+ // ==================== 输入处理 ====================
880
+ /**
881
+ * 设置输入
882
+ */
897
883
  setInput(input) {
898
884
  if (typeof input.moveX === "number") {
899
- this.lftPressed = input.moveX == -1;
900
- this.rgtPressed = input.moveX == 1;
885
+ this.lftPressed = input.moveX === -1;
886
+ this.rgtPressed = input.moveX === 1;
901
887
  this.setAnimationByPressed();
902
888
  }
903
889
  if (typeof input.moveY === "number") {
904
- this.fwdPressed = input.moveY == 1;
905
- this.bkdPressed = input.moveY == -1;
890
+ this.fwdPressed = input.moveY === 1;
891
+ this.bkdPressed = input.moveY === -1;
906
892
  this.setAnimationByPressed();
907
893
  }
908
894
  if (typeof input.lookDeltaX === "number" && typeof input.lookDeltaY === "number") {
@@ -928,10 +914,36 @@ var PlayerController = class {
928
914
  if (input.toggleFly) {
929
915
  this.isFlying = !this.isFlying;
930
916
  this.setAnimationByPressed();
931
- if (!this.isFlying && !this.playerIsOnGround) this.playPersonAnimationByName("jumping");
917
+ if (!this.isFlying && !this.playerIsOnGround) {
918
+ this.playPersonAnimationByName("jumping");
919
+ }
932
920
  }
933
921
  }
934
- // 初始化移动端摇杆控制
922
+ /**
923
+ * 事件绑定
924
+ */
925
+ onAllEvent() {
926
+ this.isupdate = true;
927
+ this.setPointerLock();
928
+ window.addEventListener("keydown", this._boundOnKeydown);
929
+ window.addEventListener("keyup", this._boundOnKeyup);
930
+ window.addEventListener("mousemove", this._mouseMove);
931
+ window.addEventListener("click", this._mouseClick);
932
+ }
933
+ /**
934
+ * 事件解绑
935
+ */
936
+ offAllEvent() {
937
+ this.isupdate = false;
938
+ document.exitPointerLock();
939
+ window.removeEventListener("keydown", this._boundOnKeydown);
940
+ window.removeEventListener("keyup", this._boundOnKeyup);
941
+ window.removeEventListener("mousemove", this._mouseMove);
942
+ window.removeEventListener("click", this._mouseClick);
943
+ }
944
+ /**
945
+ * 初始化移动端摇杆控制
946
+ */
935
947
  async initMobileControls() {
936
948
  this.controls.maxPolarAngle = Math.PI * (300 / 360);
937
949
  this.controls.touches = { ONE: null, TWO: null };
@@ -955,13 +967,9 @@ var PlayerController = class {
955
967
  });
956
968
  container.appendChild(this.joystickZoneEl);
957
969
  ["touchstart", "touchmove", "touchend", "touchcancel"].forEach((evtName) => {
958
- this.joystickZoneEl?.addEventListener(
959
- evtName,
960
- (e) => {
961
- e.preventDefault();
962
- },
963
- { passive: false }
964
- );
970
+ this.joystickZoneEl?.addEventListener(evtName, (e) => e.preventDefault(), {
971
+ passive: false
972
+ });
965
973
  });
966
974
  this.joystickManager = nipple.create({
967
975
  zone: this.joystickZoneEl,
@@ -1013,26 +1021,14 @@ var PlayerController = class {
1013
1021
  });
1014
1022
  container.appendChild(this.lookAreaEl);
1015
1023
  ["touchstart", "touchmove", "touchend", "touchcancel"].forEach((evtName) => {
1016
- this.lookAreaEl?.addEventListener(
1017
- evtName,
1018
- (e) => {
1019
- e.preventDefault();
1020
- },
1021
- { passive: false }
1022
- );
1023
- });
1024
- this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, {
1025
- passive: false
1026
- });
1027
- this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, {
1028
- passive: false
1029
- });
1030
- this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, {
1031
- passive: false
1032
- });
1033
- this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, {
1034
- passive: false
1024
+ this.lookAreaEl?.addEventListener(evtName, (e) => e.preventDefault(), {
1025
+ passive: false
1026
+ });
1035
1027
  });
1028
+ this.lookAreaEl.addEventListener("pointerdown", this.onPointerDown, { passive: false });
1029
+ this.lookAreaEl.addEventListener("pointermove", this.onPointerMove, { passive: false });
1030
+ this.lookAreaEl.addEventListener("pointerup", this.onPointerUp, { passive: false });
1031
+ this.lookAreaEl.addEventListener("pointercancel", this.onPointerUp, { passive: false });
1036
1032
  const createBtn = (rightPx, bottomPx, bgUrl) => {
1037
1033
  const btn = document.createElement("button");
1038
1034
  const styles = {
@@ -1064,13 +1060,7 @@ var PlayerController = class {
1064
1060
  Object.assign(btn.style, styles);
1065
1061
  container.appendChild(btn);
1066
1062
  ["touchstart", "touchend", "touchcancel"].forEach((evtName) => {
1067
- btn.addEventListener(
1068
- evtName,
1069
- (e) => {
1070
- e.preventDefault();
1071
- },
1072
- { passive: false }
1073
- );
1063
+ btn.addEventListener(evtName, (e) => e.preventDefault(), { passive: false });
1074
1064
  });
1075
1065
  return btn;
1076
1066
  };
@@ -1118,7 +1108,9 @@ var PlayerController = class {
1118
1108
  { passive: false }
1119
1109
  );
1120
1110
  }
1121
- // 销毁移动端摇杆控制
1111
+ /**
1112
+ * 销毁移动端摇杆控制
1113
+ */
1122
1114
  destroyMobileControls() {
1123
1115
  try {
1124
1116
  if (this.joystickManager && this.joystickManager.destroy) {
@@ -1153,6 +1145,33 @@ var PlayerController = class {
1153
1145
  console.warn("\u9500\u6BC1\u79FB\u52A8\u7AEF\u6447\u6746\u63A7\u5236\u65F6\u51FA\u9519\uFF1A", e);
1154
1146
  }
1155
1147
  }
1148
+ // ==================== 销毁 ====================
1149
+ /**
1150
+ * 销毁人物控制器
1151
+ */
1152
+ destroy() {
1153
+ this.offAllEvent();
1154
+ if (this.player) {
1155
+ this.player.remove(this.camera);
1156
+ this.scene.remove(this.player);
1157
+ }
1158
+ this.player = null;
1159
+ if (this.person) {
1160
+ this.scene.remove(this.person);
1161
+ this.person = null;
1162
+ }
1163
+ this.resetControls();
1164
+ if (this.visualizer) {
1165
+ this.scene.remove(this.visualizer);
1166
+ this.visualizer = null;
1167
+ }
1168
+ if (this.collider) {
1169
+ this.scene.remove(this.collider);
1170
+ this.collider = null;
1171
+ }
1172
+ this.destroyMobileControls();
1173
+ controllerInstance = null;
1174
+ }
1156
1175
  };
1157
1176
  function playerController() {
1158
1177
  if (!controllerInstance) controllerInstance = new PlayerController();