cutechicken commited on
Commit
dd3a438
Β·
verified Β·
1 Parent(s): 4b16f5f

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +290 -294
game.js CHANGED
@@ -25,293 +25,287 @@ const GAME_CONSTANTS = {
25
 
26
  // μ „νˆ¬κΈ° 클래슀
27
  class Fighter {
28
- constructor() {
29
- this.mesh = null;
30
- this.isLoaded = false;
31
-
32
- // 물리 속성
33
- this.position = new THREE.Vector3(0, 2000, 0);
34
- this.velocity = new THREE.Vector3(0, 0, 350); // 초기 속도 350kt
35
- this.acceleration = new THREE.Vector3(0, 0, 0);
36
- this.rotation = new THREE.Euler(0, 0, 0);
37
-
38
- // λΉ„ν–‰ μ œμ–΄
39
- this.throttle = 0.7; // 초기 μŠ€λ‘œν‹€ 70%
40
- this.speed = 350; // 초기 속도 350kt
41
- this.altitude = 2000;
42
- this.gForce = 1.0;
43
- this.health = GAME_CONSTANTS.MAX_HEALTH; // 체λ ₯ 1000
44
-
45
- // μ‘°μ’… μž…λ ₯ μ‹œμŠ€ν…œ
46
- this.pitchInput = 0;
47
- this.rollInput = 0;
48
- this.yawInput = 0;
49
-
50
- // 마우슀 λˆ„μ  μž…λ ₯
51
- this.mousePitch = 0;
52
- this.mouseRoll = 0;
53
-
54
- // λΆ€λ“œλŸ¬μš΄ νšŒμ „μ„ μœ„ν•œ λͺ©ν‘œκ°’
55
- this.targetPitch = 0;
56
- this.targetRoll = 0;
57
- this.targetYaw = 0;
58
-
59
- // 무기
60
- this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
61
- this.ammo = GAME_CONSTANTS.AMMO_COUNT;
62
- this.bullets = [];
63
- this.lastShootTime = 0;
64
- this.isMouseDown = false; // 마우슀 λˆ„λ¦„ μƒνƒœ 좔적
65
- this.gunfireAudios = []; // 기관총 μ†Œλ¦¬ λ°°μ—΄ (μ΅œλŒ€ 5개)
66
-
67
- // μŠ€ν†¨ νƒˆμΆœμ„ μœ„ν•œ Fν‚€ μƒνƒœ
68
- this.escapeKeyPressed = false;
69
- this.stallEscapeProgress = 0; // μŠ€ν†¨ νƒˆμΆœ 진행도 (0~2초)
70
-
71
- // 카메라 μ„€μ •
72
- this.cameraDistance = 250;
73
- this.cameraHeight = 30;
74
- this.cameraLag = 0.06;
75
-
76
- // κ²½κ³  μ‹œμŠ€ν…œ
77
- this.altitudeWarning = false;
78
- this.stallWarning = false;
79
- this.warningBlinkTimer = 0;
80
- this.warningBlinkState = false;
81
-
82
- // Over-G μ‹œμŠ€ν…œ
83
- this.overG = false;
84
- this.maxGForce = 9.0;
85
- this.overGTimer = 0; // Over-G 지속 μ‹œκ°„
86
-
87
- // 경고음 μ‹œμŠ€ν…œ
88
- this.warningAudios = {
89
- altitude: null,
90
- pullup: null,
91
- overg: null,
92
- stall: null,
93
- normal: null // μ—”μ§„ μ†Œλ¦¬
94
- };
95
- this.initializeWarningAudios();
96
- }
97
-
98
- // 헀딩을 0~360λ„λ‘œ μ •κ·œν™”ν•˜λŠ” 헬퍼 ν•¨μˆ˜
99
- normalizeHeading(radians) {
100
- let degrees = radians * (180 / Math.PI);
101
- while (degrees < 0) degrees += 360;
102
- while (degrees >= 360) degrees -= 360;
103
- return degrees;
104
- }
105
-
106
- // λΌλ””μ•ˆμ„ -Ο€ ~ Ο€ λ²”μœ„λ‘œ μ •κ·œν™”
107
- normalizeAngle(angle) {
108
- while (angle > Math.PI) angle -= Math.PI * 2;
109
- while (angle < -Math.PI) angle += Math.PI * 2;
110
- return angle;
111
- }
112
-
113
- initializeWarningAudios() {
114
- try {
115
- this.warningAudios.altitude = new Audio('sounds/altitude.ogg');
116
- this.warningAudios.altitude.volume = 0.75;
117
-
118
- this.warningAudios.pullup = new Audio('sounds/pullup.ogg');
119
- this.warningAudios.pullup.volume = 0.9;
120
-
121
- this.warningAudios.overg = new Audio('sounds/overg.ogg');
122
- this.warningAudios.overg.volume = 0.75;
123
-
124
- this.warningAudios.stall = new Audio('sounds/alert.ogg');
125
- this.warningAudios.stall.volume = 0.75;
126
-
127
- // μ—”μ§„ μ†Œλ¦¬ μ„€μ •
128
- this.warningAudios.normal = new Audio('sounds/normal.ogg');
129
- this.warningAudios.normal.volume = 0.5;
130
- this.warningAudios.normal.loop = true; // μ—”μ§„ μ†Œλ¦¬λŠ” 계속 반볡
131
-
132
- // κ²½κ³ μŒμ—λ§Œ ended 이벀트 λ¦¬μŠ€λ„ˆ μΆ”κ°€ (μ—”μ§„ μ†Œλ¦¬ μ œμ™Έ)
133
- Object.keys(this.warningAudios).forEach(key => {
134
- if (key !== 'normal' && this.warningAudios[key]) {
135
- this.warningAudios[key].addEventListener('ended', () => {
136
- this.updateWarningAudios();
137
- });
138
- }
139
- });
140
- } catch (e) {
141
- console.log('Warning audio initialization failed:', e);
142
- }
143
- }
144
-
145
- startEngineSound() {
146
- // μ—”μ§„ μ†Œλ¦¬ μ‹œμž‘
147
- if (this.warningAudios.normal) {
148
- this.warningAudios.normal.play().catch(e => {
149
- console.log('Engine sound failed to start:', e);
150
- });
151
- }
152
- }
153
-
154
- updateWarningAudios() {
155
- let currentWarning = null;
156
-
157
- if (this.altitude < 250) {
158
- currentWarning = 'pullup';
159
- }
160
- else if (this.altitude < 500) {
161
- currentWarning = 'altitude';
162
- }
163
- else if (this.overG) {
164
- currentWarning = 'overg';
165
- }
166
- else if (this.stallWarning) {
167
- currentWarning = 'stall';
168
- }
169
-
170
- // 경고음만 관리 (μ—”μ§„ μ†Œλ¦¬λŠ” μ œμ™Έ)
171
- Object.keys(this.warningAudios).forEach(key => {
172
- if (key !== 'normal' && key !== currentWarning && this.warningAudios[key] && !this.warningAudios[key].paused) {
173
- this.warningAudios[key].pause();
174
- this.warningAudios[key].currentTime = 0;
175
- }
176
- });
177
-
178
- if (currentWarning && this.warningAudios[currentWarning]) {
179
- if (this.warningAudios[currentWarning].paused) {
180
- this.warningAudios[currentWarning].play().catch(e => {});
181
- }
182
- }
183
- }
184
-
185
- stopAllWarningAudios() {
186
- // λͺ¨λ“  μ˜€λ””μ˜€ μ •μ§€ (μ—”μ§„ μ†Œλ¦¬ 포함)
187
- Object.values(this.warningAudios).forEach(audio => {
188
- if (audio && !audio.paused) {
189
- audio.pause();
190
- audio.currentTime = 0;
191
- }
192
- });
193
- }
194
 
195
- async initialize(scene, loader) {
196
- try {
197
- const result = await loader.loadAsync('models/f-15.glb');
198
- this.mesh = result.scene;
199
- this.mesh.position.copy(this.position);
200
- this.mesh.scale.set(2, 2, 2);
201
- this.mesh.rotation.y = Math.PI / 4;
202
-
203
- this.mesh.traverse((child) => {
204
- if (child.isMesh) {
205
- child.castShadow = true;
206
- child.receiveShadow = true;
207
- }
208
- });
209
-
210
- scene.add(this.mesh);
211
- this.isLoaded = true;
212
- console.log('F-15 μ „νˆ¬κΈ° λ‘œλ”© μ™„λ£Œ');
213
- } catch (error) {
214
- console.error('F-15 λͺ¨λΈ λ‘œλ”© μ‹€νŒ¨:', error);
215
- this.createFallbackModel(scene);
216
- }
217
- }
218
 
219
- createFallbackModel(scene) {
220
- const group = new THREE.Group();
221
-
222
- const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.5, 12, 8);
223
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
224
- const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
225
- fuselage.rotation.x = Math.PI / 2;
226
- group.add(fuselage);
227
-
228
- const wingGeometry = new THREE.BoxGeometry(16, 0.3, 4);
229
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
230
- const wings = new THREE.Mesh(wingGeometry, wingMaterial);
231
- wings.position.z = -1;
232
- group.add(wings);
233
-
234
- const tailGeometry = new THREE.BoxGeometry(0.3, 4, 3);
235
- const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
236
- const tail = new THREE.Mesh(tailGeometry, tailMaterial);
237
- tail.position.z = -5;
238
- tail.position.y = 1.5;
239
- group.add(tail);
240
-
241
- const horizontalTailGeometry = new THREE.BoxGeometry(6, 0.2, 2);
242
- const horizontalTail = new THREE.Mesh(horizontalTailGeometry, tailMaterial);
243
- horizontalTail.position.z = -5;
244
- horizontalTail.position.y = 0.5;
245
- group.add(horizontalTail);
246
-
247
- this.mesh = group;
248
- this.mesh.position.copy(this.position);
249
- this.mesh.scale.set(2, 2, 2);
250
- scene.add(this.mesh);
251
- this.isLoaded = true;
252
-
253
- console.log('Fallback μ „νˆ¬κΈ° λͺ¨λΈ 생성 μ™„λ£Œ');
254
- }
255
 
256
- updateMouseInput(deltaX, deltaY) {
257
- // Over-G μƒνƒœμ—μ„œ μŠ€ν†¨μ΄ ν•΄μ œλ˜μ§€ μ•Šμ•˜μœΌλ©΄ ν”ΌμΉ˜ μ‘°μž‘ λΆˆκ°€
258
- if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
259
- // μš”(Yaw)만 μ œν•œμ μœΌλ‘œ ν—ˆμš©
260
- const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3; // 감도 λŒ€ν­ κ°μ†Œ
261
- this.targetYaw += deltaX * sensitivity * 0.3;
262
-
263
- // ν”ΌμΉ˜λŠ” μ‘°μž‘ λΆˆκ°€
264
- // 둀도 μ œν•œμ μœΌλ‘œλ§Œ ν—ˆμš©
265
- const yawRate = deltaX * sensitivity * 0.3;
266
- this.targetRoll = -yawRate * 5; // 맀우 μ œν•œλœ λ‘€
267
-
268
- return; // μΆ”κ°€ 처리 쀑단
269
- }
270
-
271
- const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
272
-
273
- // 마우슀 YμΆ•: ν”ΌμΉ˜(기수 μƒν•˜) - 항상 λ™μΌν•˜κ²Œ μž‘λ™
274
- this.targetPitch -= deltaY * sensitivity;
275
-
276
- // 마우슀 XμΆ•: μš”(쒌우 νšŒμ „) - 항상 λ™μΌν•˜κ²Œ μž‘λ™
277
- this.targetYaw += deltaX * sensitivity * 0.8;
278
-
279
- // μš” νšŒμ „μ— λ”°λ₯Έ μžλ™ λ‘€ (뱅크 ν„΄)
280
- // 쒌우둜 νšŒμ „ν•  λ•Œ μžμ—°μŠ€λŸ½κ²Œ λ‚ κ°œκ°€ κΈ°μšΈμ–΄μ§
281
- const yawRate = deltaX * sensitivity * 0.8;
282
- this.targetRoll = -yawRate * 15; // μš” νšŒμ „λŸ‰μ— λΉ„λ‘€ν•˜μ—¬ λ‘€ λ°œμƒ
283
-
284
- // 각도 μ œν•œ
285
- const maxPitchAngle = Math.PI / 3; // 60도
286
- const maxRollAngle = Math.PI * 0.5; // 90λ„λ‘œ μ œν•œ (μžλ™ λ‘€)
287
-
288
- this.targetPitch = Math.max(-maxPitchAngle, Math.min(maxPitchAngle, this.targetPitch));
289
-
290
- // μžλ™ 둀은 μ œν•œλœ λ²”μœ„ λ‚΄μ—μ„œλ§Œ μž‘λ™
291
- if (Math.abs(this.targetRoll) < maxRollAngle) {
292
- // λ‘€ 각도 μ œν•œ 적용
293
- this.targetRoll = Math.max(-maxRollAngle, Math.min(maxRollAngle, this.targetRoll));
294
- }
295
- }
296
 
297
- updateControls(keys, deltaTime) {
298
- // W/S: μŠ€λ‘œν‹€λ§Œ μ œμ–΄ (가속/감속)
299
- if (keys.w) {
300
- this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5);
301
- }
302
- if (keys.s) {
303
- this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
304
- }
305
-
306
- // A/D: 보쑰 μš” μ œμ–΄ (λŸ¬λ”) - λ°˜μ‘μ„± κ°œμ„ 
307
- if (keys.a) {
308
- this.targetYaw -= deltaTime * 1.2;
309
- }
310
- if (keys.d) {
311
- this.targetYaw += deltaTime * 1.2;
312
- }
313
- }
314
- updatePhysics(deltaTime) {
 
315
  if (!this.mesh) return;
316
 
317
  // λΆ€λ“œλŸ¬μš΄ νšŒμ „ 보간
@@ -529,7 +523,8 @@ class Fighter {
529
  const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3λ°° 쀑λ ₯
530
  this.velocity.y -= gravityAcceleration;
531
  }
532
- // 속도 벑터 계산 - μΏΌν„°λ‹ˆμ–Έμ„ μ‚¬μš©ν•˜μ—¬ 짐벌 락 μ™„μ „ νšŒν”Ό
 
533
  const noseDirection = new THREE.Vector3(0, 0, 1);
534
 
535
  // μΏΌν„°λ‹ˆμ–Έ 기반 νšŒμ „ μ‹œμŠ€ν…œ
@@ -550,10 +545,11 @@ class Fighter {
550
  yawQuat.setFromAxisAngle(yAxis, this.rotation.y);
551
  rollQuat.setFromAxisAngle(zAxis, this.rotation.z);
552
 
553
- // μΏΌν„°λ‹ˆμ–Έ ν•©μ„± (항곡기 ν‘œμ€€ μˆœμ„œ: Yaw -> Pitch -> Roll)
554
- quaternion.multiply(yawQuat);
555
- quaternion.multiply(pitchQuat);
556
  quaternion.multiply(rollQuat);
 
 
557
 
558
  // λ°©ν–₯ 벑터에 νšŒμ „ 적용
559
  noseDirection.applyQuaternion(quaternion);
@@ -626,10 +622,10 @@ class Fighter {
626
  meshYawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y + 3 * Math.PI / 2);
627
  meshRollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
628
 
629
- // μΏΌν„°λ‹ˆμ–Έ ν•©μ„±
630
- meshQuaternion.multiply(meshYawQuat);
631
- meshQuaternion.multiply(meshPitchQuat);
632
  meshQuaternion.multiply(meshRollQuat);
 
 
633
 
634
  this.mesh.quaternion.copy(meshQuaternion);
635
 
@@ -680,9 +676,9 @@ class Fighter {
680
  yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
681
  rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
682
 
683
- quaternion.multiply(yawQuat);
684
- quaternion.multiply(pitchQuat);
685
  quaternion.multiply(rollQuat);
 
 
686
 
687
  muzzleOffset.applyQuaternion(quaternion);
688
  bullet.position.copy(this.position).add(muzzleOffset);
@@ -772,9 +768,9 @@ class Fighter {
772
  yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
773
  rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
774
 
775
- quaternion.multiply(yawQuat);
776
- quaternion.multiply(pitchQuat);
777
  quaternion.multiply(rollQuat);
 
 
778
 
779
  backward.applyQuaternion(quaternion);
780
  up.applyQuaternion(quaternion);
 
25
 
26
  // μ „νˆ¬κΈ° 클래슀
27
  class Fighter {
28
+ constructor() {
29
+ this.mesh = null;
30
+ this.isLoaded = false;
31
+
32
+ // 물리 속성
33
+ this.position = new THREE.Vector3(0, 2000, 0);
34
+ this.velocity = new THREE.Vector3(0, 0, 350); // 초기 속도 350kt
35
+ this.acceleration = new THREE.Vector3(0, 0, 0);
36
+ this.rotation = new THREE.Euler(0, 0, 0, 'YXZ'); // 항곡기 ν‘œμ€€ 였일러 μˆœμ„œ
37
+
38
+ // λΉ„ν–‰ μ œμ–΄
39
+ this.throttle = 0.7; // 초기 μŠ€λ‘œν‹€ 70%
40
+ this.speed = 350; // 초기 속도 350kt
41
+ this.altitude = 2000;
42
+ this.gForce = 1.0;
43
+ this.health = GAME_CONSTANTS.MAX_HEALTH; // 체λ ₯ 1000
44
+
45
+ // μ‘°μ’… μž…λ ₯ μ‹œμŠ€ν…œ
46
+ this.pitchInput = 0;
47
+ this.rollInput = 0;
48
+ this.yawInput = 0;
49
+
50
+ // 마우슀 λˆ„μ  μž…λ ₯
51
+ this.mousePitch = 0;
52
+ this.mouseRoll = 0;
53
+
54
+ // λΆ€λ“œλŸ¬μš΄ νšŒμ „μ„ μœ„ν•œ λͺ©ν‘œκ°’
55
+ this.targetPitch = 0;
56
+ this.targetRoll = 0;
57
+ this.targetYaw = 0;
58
+
59
+ // 무기
60
+ this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
61
+ this.ammo = GAME_CONSTANTS.AMMO_COUNT;
62
+ this.bullets = [];
63
+ this.lastShootTime = 0;
64
+ this.isMouseDown = false; // 마우슀 λˆ„λ¦„ μƒνƒœ 좔적
65
+ this.gunfireAudios = []; // 기관총 μ†Œλ¦¬ λ°°μ—΄ (μ΅œλŒ€ 5개)
66
+
67
+ // μŠ€ν†¨ νƒˆμΆœμ„ μœ„ν•œ Fν‚€ μƒνƒœ
68
+ this.escapeKeyPressed = false;
69
+ this.stallEscapeProgress = 0; // μŠ€ν†¨ νƒˆμΆœ 진행도 (0~2초)
70
+
71
+ // 카메라 μ„€μ •
72
+ this.cameraDistance = 250;
73
+ this.cameraHeight = 30;
74
+ this.cameraLag = 0.06;
75
+
76
+ // κ²½κ³  μ‹œμŠ€ν…œ
77
+ this.altitudeWarning = false;
78
+ this.stallWarning = false;
79
+ this.warningBlinkTimer = 0;
80
+ this.warningBlinkState = false;
81
+
82
+ // Over-G μ‹œμŠ€ν…œ
83
+ this.overG = false;
84
+ this.maxGForce = 9.0;
85
+ this.overGTimer = 0; // Over-G 지속 μ‹œκ°„
86
+
87
+ // 경고음 μ‹œμŠ€ν…œ
88
+ this.warningAudios = {
89
+ altitude: null,
90
+ pullup: null,
91
+ overg: null,
92
+ stall: null,
93
+ normal: null // μ—”μ§„ μ†Œλ¦¬
94
+ };
95
+ this.initializeWarningAudios();
96
+ }
97
+
98
+ // 헀딩을 0~360λ„λ‘œ μ •κ·œν™”ν•˜λŠ” 헬퍼 ν•¨μˆ˜
99
+ normalizeHeading(radians) {
100
+ let degrees = radians * (180 / Math.PI);
101
+ while (degrees < 0) degrees += 360;
102
+ while (degrees >= 360) degrees -= 360;
103
+ return degrees;
104
+ }
105
+
106
+ // λΌλ””μ•ˆμ„ -Ο€ ~ Ο€ λ²”μœ„λ‘œ μ •κ·œν™”
107
+ normalizeAngle(angle) {
108
+ while (angle > Math.PI) angle -= Math.PI * 2;
109
+ while (angle < -Math.PI) angle += Math.PI * 2;
110
+ return angle;
111
+ }
112
+
113
+ initializeWarningAudios() {
114
+ try {
115
+ this.warningAudios.altitude = new Audio('sounds/altitude.ogg');
116
+ this.warningAudios.altitude.volume = 0.75;
117
+
118
+ this.warningAudios.pullup = new Audio('sounds/pullup.ogg');
119
+ this.warningAudios.pullup.volume = 0.9;
120
+
121
+ this.warningAudios.overg = new Audio('sounds/overg.ogg');
122
+ this.warningAudios.overg.volume = 0.75;
123
+
124
+ this.warningAudios.stall = new Audio('sounds/alert.ogg');
125
+ this.warningAudios.stall.volume = 0.75;
126
+
127
+ // μ—”μ§„ μ†Œλ¦¬ μ„€μ •
128
+ this.warningAudios.normal = new Audio('sounds/normal.ogg');
129
+ this.warningAudios.normal.volume = 0.5;
130
+ this.warningAudios.normal.loop = true; // μ—”μ§„ μ†Œλ¦¬λŠ” 계속 반볡
131
+
132
+ // κ²½κ³ μŒμ—λ§Œ ended 이벀트 λ¦¬μŠ€λ„ˆ μΆ”κ°€ (μ—”μ§„ μ†Œλ¦¬ μ œμ™Έ)
133
+ Object.keys(this.warningAudios).forEach(key => {
134
+ if (key !== 'normal' && this.warningAudios[key]) {
135
+ this.warningAudios[key].addEventListener('ended', () => {
136
+ this.updateWarningAudios();
137
+ });
138
+ }
139
+ });
140
+ } catch (e) {
141
+ console.log('Warning audio initialization failed:', e);
142
+ }
143
+ }
144
+
145
+ startEngineSound() {
146
+ // μ—”μ§„ μ†Œλ¦¬ μ‹œμž‘
147
+ if (this.warningAudios.normal) {
148
+ this.warningAudios.normal.play().catch(e => {
149
+ console.log('Engine sound failed to start:', e);
150
+ });
151
+ }
152
+ }
153
+
154
+ updateWarningAudios() {
155
+ let currentWarning = null;
156
+
157
+ if (this.altitude < 250) {
158
+ currentWarning = 'pullup';
159
+ }
160
+ else if (this.altitude < 500) {
161
+ currentWarning = 'altitude';
162
+ }
163
+ else if (this.overG) {
164
+ currentWarning = 'overg';
165
+ }
166
+ else if (this.stallWarning) {
167
+ currentWarning = 'stall';
168
+ }
169
+
170
+ // 경고음만 관리 (μ—”μ§„ μ†Œλ¦¬λŠ” μ œμ™Έ)
171
+ Object.keys(this.warningAudios).forEach(key => {
172
+ if (key !== 'normal' && key !== currentWarning && this.warningAudios[key] && !this.warningAudios[key].paused) {
173
+ this.warningAudios[key].pause();
174
+ this.warningAudios[key].currentTime = 0;
175
+ }
176
+ });
177
+
178
+ if (currentWarning && this.warningAudios[currentWarning]) {
179
+ if (this.warningAudios[currentWarning].paused) {
180
+ this.warningAudios[currentWarning].play().catch(e => {});
181
+ }
182
+ }
183
+ }
184
+
185
+ stopAllWarningAudios() {
186
+ // λͺ¨λ“  μ˜€λ””μ˜€ μ •μ§€ (μ—”μ§„ μ†Œλ¦¬ 포함)
187
+ Object.values(this.warningAudios).forEach(audio => {
188
+ if (audio && !audio.paused) {
189
+ audio.pause();
190
+ audio.currentTime = 0;
191
+ }
192
+ });
193
+ }
194
 
195
+ async initialize(scene, loader) {
196
+ try {
197
+ const result = await loader.loadAsync('models/f-15.glb');
198
+ this.mesh = result.scene;
199
+ this.mesh.position.copy(this.position);
200
+ this.mesh.scale.set(2, 2, 2);
201
+ this.mesh.rotation.y = Math.PI / 4;
202
+
203
+ this.mesh.traverse((child) => {
204
+ if (child.isMesh) {
205
+ child.castShadow = true;
206
+ child.receiveShadow = true;
207
+ }
208
+ });
209
+
210
+ scene.add(this.mesh);
211
+ this.isLoaded = true;
212
+ console.log('F-15 μ „νˆ¬κΈ° λ‘œλ”© μ™„λ£Œ');
213
+ } catch (error) {
214
+ console.error('F-15 λͺ¨λΈ λ‘œλ”© μ‹€νŒ¨:', error);
215
+ this.createFallbackModel(scene);
216
+ }
217
+ }
218
 
219
+ createFallbackModel(scene) {
220
+ const group = new THREE.Group();
221
+
222
+ const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.5, 12, 8);
223
+ const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
224
+ const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
225
+ fuselage.rotation.x = Math.PI / 2;
226
+ group.add(fuselage);
227
+
228
+ const wingGeometry = new THREE.BoxGeometry(16, 0.3, 4);
229
+ const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
230
+ const wings = new THREE.Mesh(wingGeometry, wingMaterial);
231
+ wings.position.z = -1;
232
+ group.add(wings);
233
+
234
+ const tailGeometry = new THREE.BoxGeometry(0.3, 4, 3);
235
+ const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
236
+ const tail = new THREE.Mesh(tailGeometry, tailMaterial);
237
+ tail.position.z = -5;
238
+ tail.position.y = 1.5;
239
+ group.add(tail);
240
+
241
+ const horizontalTailGeometry = new THREE.BoxGeometry(6, 0.2, 2);
242
+ const horizontalTail = new THREE.Mesh(horizontalTailGeometry, tailMaterial);
243
+ horizontalTail.position.z = -5;
244
+ horizontalTail.position.y = 0.5;
245
+ group.add(horizontalTail);
246
+
247
+ this.mesh = group;
248
+ this.mesh.position.copy(this.position);
249
+ this.mesh.scale.set(2, 2, 2);
250
+ scene.add(this.mesh);
251
+ this.isLoaded = true;
252
+
253
+ console.log('Fallback μ „νˆ¬κΈ° λͺ¨λΈ 생성 μ™„λ£Œ');
254
+ }
255
 
256
+ updateMouseInput(deltaX, deltaY) {
257
+ // Over-G μƒνƒœμ—μ„œ μŠ€ν†¨μ΄ ν•΄μ œλ˜μ§€ μ•Šμ•˜μœΌλ©΄ ν”ΌμΉ˜ μ‘°μž‘ λΆˆκ°€
258
+ if (this.overG && this.overGTimer > 1.0 && this.stallWarning) {
259
+ // μš”(Yaw)만 μ œν•œμ μœΌλ‘œ ν—ˆμš©
260
+ const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 0.3;
261
+ this.targetYaw += deltaX * sensitivity * 0.3;
262
+ return;
263
+ }
264
+
265
+ const sensitivity = GAME_CONSTANTS.MOUSE_SENSITIVITY * 1.0;
266
+
267
+ // 마우슀 μž…λ ₯을 κ°μ†λ„λ‘œ λ³€ν™˜
268
+ const pitchRate = -deltaY * sensitivity;
269
+ const yawRate = deltaX * sensitivity * 0.8;
270
+
271
+ // ν˜„μž¬ μƒνƒœμ—μ„œμ˜ ν”ΌμΉ˜ μ œν•œ
272
+ this.targetPitch += pitchRate;
273
+ this.targetYaw += yawRate;
274
+
275
+ // μš” νšŒμ „μ— λ”°λ₯Έ μžμ—°μŠ€λŸ¬μš΄ 뱅크 (λ‘€)
276
+ // μ‹€μ œ ν•­κ³΅κΈ°μ²˜λŸΌ μ„ νšŒ μ‹œ μžλ™μœΌλ‘œ κΈ°μšΈμ–΄μ§
277
+ const targetBankAngle = -yawRate * 15; // μš” 속도에 λΉ„λ‘€ν•œ 뱅크 각도
278
+
279
+ // 둀을 λͺ©ν‘œ 뱅크 κ°λ„λ‘œ λΆ€λ“œλŸ½κ²Œ μ „ν™˜
280
+ this.targetRoll = THREE.MathUtils.lerp(this.targetRoll, targetBankAngle, 0.1);
281
+
282
+ // 각도 μ œν•œ
283
+ const maxPitchAngle = Math.PI / 3; // 60도
284
+ const maxRollAngle = Math.PI * 0.5; // 90도
285
+
286
+ this.targetPitch = Math.max(-maxPitchAngle, Math.min(maxPitchAngle, this.targetPitch));
287
+ this.targetRoll = Math.max(-maxRollAngle, Math.min(maxRollAngle, this.targetRoll));
288
+ }
 
 
 
 
 
 
 
289
 
290
+ updateControls(keys, deltaTime) {
291
+ // W/S: μŠ€λ‘œν‹€λ§Œ μ œμ–΄ (가속/감속)
292
+ if (keys.w) {
293
+ this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.5);
294
+ }
295
+ if (keys.s) {
296
+ this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.5);
297
+ }
298
+
299
+ // A/D: 보쑰 μš” μ œμ–΄ (λŸ¬λ”) - λ°˜μ‘μ„± κ°œμ„ 
300
+ if (keys.a) {
301
+ this.targetYaw -= deltaTime * 1.2;
302
+ }
303
+ if (keys.d) {
304
+ this.targetYaw += deltaTime * 1.2;
305
+ }
306
+ }
307
+
308
+ updatePhysics(deltaTime) {
309
  if (!this.mesh) return;
310
 
311
  // λΆ€λ“œλŸ¬μš΄ νšŒμ „ 보간
 
523
  const gravityAcceleration = GAME_CONSTANTS.GRAVITY * deltaTime * 3.0; // 3λ°° 쀑λ ₯
524
  this.velocity.y -= gravityAcceleration;
525
  }
526
+
527
+ // 속도 벑터 계산 - μΏΌν„°λ‹ˆμ–Έμ„ μ‚¬μš©ν•˜μ—¬ 짐벌 락 μ™„μ „ νšŒν”Ό
528
  const noseDirection = new THREE.Vector3(0, 0, 1);
529
 
530
  // μΏΌν„°λ‹ˆμ–Έ 기반 νšŒμ „ μ‹œμŠ€ν…œ
 
545
  yawQuat.setFromAxisAngle(yAxis, this.rotation.y);
546
  rollQuat.setFromAxisAngle(zAxis, this.rotation.z);
547
 
548
+ // μΏΌν„°λ‹ˆμ–Έ ν•©μ„± (μ˜¬λ°”λ₯Έ 항곡기 μˆœμ„œ: Roll -> Pitch -> Yaw)
549
+ // ν•­κ³΅κΈ°λŠ” λ¨Όμ € λ‘€, κ·Έ λ‹€μŒ ν”ΌμΉ˜, λ§ˆμ§€λ§‰μœΌλ‘œ μš” μˆœμ„œλ‘œ νšŒμ „
 
550
  quaternion.multiply(rollQuat);
551
+ quaternion.multiply(pitchQuat);
552
+ quaternion.multiply(yawQuat);
553
 
554
  // λ°©ν–₯ 벑터에 νšŒμ „ 적용
555
  noseDirection.applyQuaternion(quaternion);
 
622
  meshYawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y + 3 * Math.PI / 2);
623
  meshRollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
624
 
625
+ // μΏΌν„°λ‹ˆμ–Έ ν•©μ„± (μ˜¬λ°”λ₯Έ μˆœμ„œ)
 
 
626
  meshQuaternion.multiply(meshRollQuat);
627
+ meshQuaternion.multiply(meshPitchQuat);
628
+ meshQuaternion.multiply(meshYawQuat);
629
 
630
  this.mesh.quaternion.copy(meshQuaternion);
631
 
 
676
  yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
677
  rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
678
 
 
 
679
  quaternion.multiply(rollQuat);
680
+ quaternion.multiply(pitchQuat);
681
+ quaternion.multiply(yawQuat);
682
 
683
  muzzleOffset.applyQuaternion(quaternion);
684
  bullet.position.copy(this.position).add(muzzleOffset);
 
768
  yawQuat.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.rotation.y);
769
  rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.rotation.z);
770
 
 
 
771
  quaternion.multiply(rollQuat);
772
+ quaternion.multiply(pitchQuat);
773
+ quaternion.multiply(yawQuat);
774
 
775
  backward.applyQuaternion(quaternion);
776
  up.applyQuaternion(quaternion);