cutechicken commited on
Commit
46b60f2
ยท
verified ยท
1 Parent(s): 916e77f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +350 -895
index.html CHANGED
@@ -1,944 +1,399 @@
1
- import * as THREE from 'three';
2
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
3
-
4
- // ๊ฒŒ์ž„ ์ƒ์ˆ˜
5
- const GAME_CONSTANTS = {
6
- MISSION_DURATION: 180,
7
- MAP_SIZE: 15000,
8
- MAX_ALTITUDE: 12000,
9
- MIN_ALTITUDE: 100,
10
- MAX_SPEED: 800,
11
- STALL_SPEED: 120,
12
- GRAVITY: 9.8,
13
- MOUSE_SENSITIVITY: 0.001, // ๋งˆ์šฐ์Šค ๊ฐ๋„ ๋‚ฎ์ถค
14
- MAX_G_FORCE: 12.0,
15
- ENEMY_COUNT: 4,
16
- MISSILE_COUNT: 6,
17
- AMMO_COUNT: 300
18
- };
19
-
20
- // ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค
21
- class Fighter {
22
- constructor() {
23
- this.mesh = null;
24
- this.isLoaded = false;
25
-
26
- // ๋ฌผ๋ฆฌ ์†์„ฑ
27
- this.position = new THREE.Vector3(0, 2000, 0);
28
- this.velocity = new THREE.Vector3(0, 0, 150); // ์ดˆ๊ธฐ ์ „์ง„ ์†๋„
29
- this.acceleration = new THREE.Vector3(0, 0, 0);
30
- this.rotation = new THREE.Euler(0, 0, 0);
31
-
32
- // ๋น„ํ–‰ ์ œ์–ด (์›Œ์ฌ๋” ์Šคํƒ€์ผ)
33
- this.baseSpeed = 150; // ๊ธฐ๋ณธ ์†๋„
34
- this.throttle = 0.5; // ๊ธฐ๋ณธ ์Šค๋กœํ‹€ 50%
35
- this.speed = 150;
36
- this.altitude = 2000;
37
- this.gForce = 1.0;
38
- this.health = 100;
39
-
40
- // ์กฐ์ข… ์ž…๋ ฅ ์‹œ์Šคํ…œ
41
- this.pitchInput = 0; // ๋งˆ์šฐ์Šค Y -> ๊ธฐ์ˆ˜ ์ƒํ•˜
42
- this.rollInput = 0; // ๋งˆ์šฐ์Šค X -> ์ขŒ์šฐ ํšŒ์ „ (๋กค)
43
- this.yawInput = 0; // A/D ํ‚ค -> ๋Ÿฌ๋”
44
-
45
- // ๋งˆ์šฐ์Šค ๋ˆ„์  ์ž…๋ ฅ
46
- this.mousePitch = 0;
47
- this.mouseRoll = 0;
48
-
49
- // ๋ถ€๋“œ๋Ÿฌ์šด ํšŒ์ „์„ ์œ„ํ•œ ๋ชฉํ‘œ๊ฐ’
50
- this.targetPitch = 0;
51
- this.targetRoll = 0;
52
- this.targetYaw = 0;
53
-
54
- // ๋ฌด๊ธฐ
55
- this.missiles = GAME_CONSTANTS.MISSILE_COUNT;
56
- this.ammo = GAME_CONSTANTS.AMMO_COUNT;
57
- this.bullets = [];
58
- this.lastShootTime = 0;
59
-
60
- // ์นด๋ฉ”๋ผ ์„ค์ •
61
- this.cameraDistance = 40;
62
- this.cameraHeight = 10;
63
- this.cameraLag = 0.08; // ์นด๋ฉ”๋ผ ๋”ฐ๋ผ์˜ค๋Š” ์†๋„
64
- }
65
-
66
- async initialize(scene, loader) {
67
- try {
68
- const result = await loader.loadAsync('models/f-15.glb');
69
- this.mesh = result.scene;
70
- this.mesh.position.copy(this.position);
71
- this.mesh.scale.set(2, 2, 2);
72
-
73
- // ๊ทธ๋ฆผ์ž ์„ค์ •
74
- this.mesh.traverse((child) => {
75
- if (child.isMesh) {
76
- child.castShadow = true;
77
- child.receiveShadow = true;
78
- }
79
- });
80
-
81
- scene.add(this.mesh);
82
- this.isLoaded = true;
83
-
84
- console.log('F-15 ์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์™„๋ฃŒ');
85
- } catch (error) {
86
- console.error('F-15 ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ:', error);
87
- this.createFallbackModel(scene);
88
  }
89
- }
90
-
91
- createFallbackModel(scene) {
92
- const group = new THREE.Group();
93
-
94
- // ๋™์ฒด
95
- const fuselageGeometry = new THREE.CylinderGeometry(0.8, 1.5, 12, 8);
96
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x606060 });
97
- const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
98
- fuselage.rotation.x = Math.PI / 2;
99
- group.add(fuselage);
100
-
101
- // ์ฃผ์ต
102
- const wingGeometry = new THREE.BoxGeometry(16, 0.3, 4);
103
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
104
- const wings = new THREE.Mesh(wingGeometry, wingMaterial);
105
- wings.position.z = -1;
106
- group.add(wings);
107
-
108
- // ์ˆ˜์ง ์•ˆ์ •ํŒ
109
- const tailGeometry = new THREE.BoxGeometry(0.3, 4, 3);
110
- const tailMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 });
111
- const tail = new THREE.Mesh(tailGeometry, tailMaterial);
112
- tail.position.z = -5;
113
- tail.position.y = 1.5;
114
- group.add(tail);
115
-
116
- // ์ˆ˜ํ‰ ์•ˆ์ •ํŒ
117
- const horizontalTailGeometry = new THREE.BoxGeometry(6, 0.2, 2);
118
- const horizontalTail = new THREE.Mesh(horizontalTailGeometry, tailMaterial);
119
- horizontalTail.position.z = -5;
120
- horizontalTail.position.y = 0.5;
121
- group.add(horizontalTail);
122
-
123
- this.mesh = group;
124
- this.mesh.position.copy(this.position);
125
- this.mesh.scale.set(2, 2, 2);
126
- scene.add(this.mesh);
127
- this.isLoaded = true;
128
-
129
- console.log('Fallback ์ „ํˆฌ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
130
- }
131
-
132
- // ์›Œ์ฌ๋” ์Šคํƒ€์ผ ๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
133
- updateMouseInput(deltaX, deltaY) {
134
- // ๋งˆ์šฐ์Šค X = ๋กค (์ขŒ์šฐ ๊ธฐ์šธ๊ธฐ)
135
- // ๋งˆ์šฐ์Šค Y = ํ”ผ์น˜ (๊ธฐ์ˆ˜ ์ƒํ•˜)
136
- this.mouseRoll += deltaX * GAME_CONSTANTS.MOUSE_SENSITIVITY;
137
- this.mousePitch += deltaY * GAME_CONSTANTS.MOUSE_SENSITIVITY;
138
-
139
- // ์ž…๋ ฅ ์ œํ•œ (ยฑ1)
140
- this.mouseRoll = Math.max(-1, Math.min(1, this.mouseRoll));
141
- this.mousePitch = Math.max(-1, Math.min(1, this.mousePitch));
142
- }
143
-
144
- // ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
145
- updateControls(keys, deltaTime) {
146
- // W/S: ์Šค๋กœํ‹€ ์กฐ์ ˆ
147
- if (keys.w) {
148
- this.throttle = Math.min(1.0, this.throttle + deltaTime * 0.8);
149
- }
150
- if (keys.s) {
151
- this.throttle = Math.max(0.1, this.throttle - deltaTime * 0.8);
152
  }
153
 
154
- // A/D: ๋Ÿฌ๋” (์š” ํšŒ์ „)
155
- this.yawInput = 0;
156
- if (keys.a) this.yawInput = -0.5;
157
- if (keys.d) this.yawInput = 0.5;
158
-
159
- // ํ˜„์žฌ ์ž…๋ ฅ์„ ์กฐ์ข… ์ž…๋ ฅ์œผ๋กœ ์„ค์ •
160
- this.pitchInput = this.mousePitch;
161
- this.rollInput = this.mouseRoll;
162
-
163
- // ๋ชฉํ‘œ ํšŒ์ „๊ฐ ๊ณ„์‚ฐ (๋ถ€๋“œ๋Ÿฌ์šด ์ œ์–ด)
164
- const maxPitchAngle = Math.PI / 6; // 30๋„
165
- const maxRollAngle = Math.PI / 4; // 45๋„
166
-
167
- this.targetPitch = this.pitchInput * maxPitchAngle;
168
- this.targetRoll = this.rollInput * maxRollAngle;
169
- this.targetYaw += this.yawInput * deltaTime * 1.5;
170
-
171
- // ๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ฐ์‡ 
172
- this.mouseRoll *= 0.95;
173
- this.mousePitch *= 0.95;
174
- }
175
-
176
- // ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
177
- updatePhysics(deltaTime) {
178
- if (!this.mesh) return;
179
-
180
- // ํšŒ์ „ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณด๊ฐ„
181
- const rotationSpeed = deltaTime * 3.0;
182
- this.rotation.x = THREE.MathUtils.lerp(this.rotation.x, this.targetPitch, rotationSpeed);
183
- this.rotation.y = THREE.MathUtils.lerp(this.rotation.y, this.targetYaw, rotationSpeed);
184
- this.rotation.z = THREE.MathUtils.lerp(this.rotation.z, this.targetRoll, rotationSpeed);
185
-
186
- // ์Šค๋กœํ‹€์— ๋”ฐ๋ฅธ ์†๋„ ๊ณ„์‚ฐ
187
- const minSpeed = 100; // ์ตœ์†Œ ์†๋„
188
- const maxSpeed = 300; // ์ตœ๋Œ€ ์†๋„
189
- const targetSpeed = minSpeed + (maxSpeed - minSpeed) * this.throttle;
190
-
191
- // ์†๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ™”
192
- this.speed = THREE.MathUtils.lerp(this.speed, targetSpeed, deltaTime * 2);
193
-
194
- // ์ „์ง„ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
195
- const forward = new THREE.Vector3(0, 0, 1);
196
- forward.applyEuler(this.rotation);
197
-
198
- // ์†๋„ ๋ฒกํ„ฐ ์„ค์ • (ํ•ญ์ƒ ์ „์ง„)
199
- this.velocity = forward.multiplyScalar(this.speed);
200
-
201
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
202
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
203
-
204
- // ๊ณ ๋„ ์ œํ•œ
205
- if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
206
- this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
207
- if (this.velocity.y < 0) this.velocity.y = 0;
208
  }
209
-
210
- if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
211
- this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
212
- if (this.velocity.y > 0) this.velocity.y = 0;
 
 
 
 
 
 
213
  }
214
-
215
- // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜ (๋ฌดํ•œ ๋งต)
216
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
217
- if (this.position.x > mapLimit) this.position.x = -mapLimit;
218
- if (this.position.x < -mapLimit) this.position.x = mapLimit;
219
- if (this.position.z > mapLimit) this.position.z = -mapLimit;
220
- if (this.position.z < -mapLimit) this.position.z = mapLimit;
221
-
222
- // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
223
- this.mesh.position.copy(this.position);
224
- this.mesh.rotation.copy(this.rotation);
225
-
226
- // ํ˜„์žฌ ๊ฐ’ ์—…๋ฐ์ดํŠธ
227
- this.altitude = this.position.y;
228
-
229
- // G-Force ๊ณ„์‚ฐ (ํšŒ์ „ ๊ฐ•๋„์— ๋”ฐ๋ผ)
230
- const rotationIntensity = Math.abs(this.targetPitch) + Math.abs(this.targetRoll);
231
- this.gForce = 1.0 + rotationIntensity * 3;
232
- }
233
 
234
- // ๋ฐœ์‚ฌ ์‹œ์Šคํ…œ
235
- shoot(scene) {
236
- const currentTime = Date.now();
237
- if (currentTime - this.lastShootTime < 100 || this.ammo <= 0) return;
238
-
239
- this.lastShootTime = currentTime;
240
- this.ammo--;
241
-
242
- // ์ด๏ฟฝ๏ฟฝ๏ฟฝ ์ƒ์„ฑ
243
- const bulletGeometry = new THREE.SphereGeometry(0.2);
244
- const bulletMaterial = new THREE.MeshBasicMaterial({
245
- color: 0xffff00,
246
- emissive: 0xffff00,
247
- emissiveIntensity: 0.7
248
- });
249
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
250
-
251
- // ์ด๊ตฌ ์œ„์น˜ (์ „ํˆฌ๊ธฐ ์•ž์ชฝ)
252
- const muzzleOffset = new THREE.Vector3(0, 0, 8);
253
- muzzleOffset.applyEuler(this.rotation);
254
- bullet.position.copy(this.position).add(muzzleOffset);
255
-
256
- // ์ด์•Œ ์†๋„ (์ „ํˆฌ๊ธฐ ์†๋„ + ์ด์•Œ ์†๋„)
257
- const bulletSpeed = 1000;
258
- const direction = new THREE.Vector3(0, 0, 1);
259
- direction.applyEuler(this.rotation);
260
- bullet.velocity = direction.multiplyScalar(bulletSpeed).add(this.velocity);
261
-
262
- scene.add(bullet);
263
- this.bullets.push(bullet);
264
-
265
- // ๋ฐœ์‚ฌ์Œ
266
- try {
267
- const audio = new Audio('sounds/gunfire.ogg');
268
- audio.volume = 0.3;
269
- audio.play();
270
- } catch (e) {
271
- console.log('Sound file not found');
272
  }
273
- }
274
 
275
- // ์ด์•Œ ์—…๋ฐ์ดํŠธ
276
- updateBullets(scene, deltaTime) {
277
- for (let i = this.bullets.length - 1; i >= 0; i--) {
278
- const bullet = this.bullets[i];
279
- bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
280
-
281
- // ์ด์•Œ ์ œ๊ฑฐ ์กฐ๊ฑด
282
- if (bullet.position.distanceTo(this.position) > 8000 ||
283
- bullet.position.y < 0 ||
284
- bullet.position.y > GAME_CONSTANTS.MAX_ALTITUDE + 500) {
285
- scene.remove(bullet);
286
- this.bullets.splice(i, 1);
287
- }
288
  }
289
- }
290
 
291
- // ํ”ผํ•ด ์‹œ์Šคํ…œ
292
- takeDamage(damage) {
293
- this.health -= damage;
294
- return this.health <= 0;
295
- }
296
-
297
- // ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ (์ „ํˆฌ๊ธฐ ๋’ค์ชฝ์—์„œ ์ถ”์ )
298
- getCameraPosition() {
299
- // ์ „ํˆฌ๊ธฐ ๋’ค์ชฝ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ
300
- const backward = new THREE.Vector3(0, 0, -1);
301
- backward.applyEuler(this.rotation);
302
-
303
- // ์นด๋ฉ”๋ผ ์œ„์น˜ = ์ „ํˆฌ๊ธฐ ์œ„์น˜ + ๋’ค์ชฝ ๋ฐฉํ–ฅ * ๊ฑฐ๋ฆฌ + ๋†’์ด
304
- const cameraPos = this.position.clone()
305
- .add(backward.multiplyScalar(this.cameraDistance))
306
- .add(new THREE.Vector3(0, this.cameraHeight, 0));
307
-
308
- return cameraPos;
309
- }
310
-
311
- // ์นด๋ฉ”๋ผ ํƒ€๊ฒŸ (์ „ํˆฌ๊ธฐ๋ฅผ ๋ฐ”๋ผ๋ด„)
312
- getCameraTarget() {
313
- return this.position.clone().add(new THREE.Vector3(0, 0, 0));
314
- }
315
- }
316
-
317
- // ์  ์ „ํˆฌ๊ธฐ ํด๋ž˜์Šค (๊ฐ„๋‹จํ•œ ๋ฒ„์ „)
318
- class EnemyFighter {
319
- constructor(scene, position) {
320
- this.mesh = null;
321
- this.isLoaded = false;
322
- this.scene = scene;
323
- this.position = position.clone();
324
- this.velocity = new THREE.Vector3(0, 0, 120);
325
- this.rotation = new THREE.Euler(0, 0, 0);
326
- this.health = 100;
327
- this.speed = 120;
328
- this.bullets = [];
329
- this.lastShootTime = 0;
330
-
331
- // AI ์ƒํƒœ
332
- this.aiState = 'patrol';
333
- this.targetPosition = position.clone();
334
- this.patrolCenter = position.clone();
335
- this.patrolRadius = 2000;
336
- this.lastStateChange = 0;
337
- }
338
-
339
- async initialize(loader) {
340
- try {
341
- const result = await loader.loadAsync('models/mig-29.glb');
342
- this.mesh = result.scene;
343
- this.mesh.position.copy(this.position);
344
- this.mesh.scale.set(1.5, 1.5, 1.5);
345
-
346
- this.mesh.traverse((child) => {
347
- if (child.isMesh) {
348
- child.castShadow = true;
349
- child.receiveShadow = true;
350
- }
351
- });
352
-
353
- this.scene.add(this.mesh);
354
- this.isLoaded = true;
355
-
356
- console.log('MiG-29 ์ ๊ธฐ ๋กœ๋”ฉ ์™„๋ฃŒ');
357
- } catch (error) {
358
- console.error('MiG-29 ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ:', error);
359
- this.createFallbackModel();
360
  }
361
- }
362
-
363
- createFallbackModel() {
364
- const group = new THREE.Group();
365
-
366
- const fuselageGeometry = new THREE.CylinderGeometry(0.6, 1.0, 8, 8);
367
- const fuselageMaterial = new THREE.MeshLambertMaterial({ color: 0x800000 });
368
- const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);
369
- fuselage.rotation.x = Math.PI / 2;
370
- group.add(fuselage);
371
-
372
- const wingGeometry = new THREE.BoxGeometry(12, 0.3, 3);
373
- const wingMaterial = new THREE.MeshLambertMaterial({ color: 0x600000 });
374
- const wings = new THREE.Mesh(wingGeometry, wingMaterial);
375
- wings.position.z = -0.5;
376
- group.add(wings);
377
-
378
- this.mesh = group;
379
- this.mesh.position.copy(this.position);
380
- this.mesh.scale.set(1.5, 1.5, 1.5);
381
- this.scene.add(this.mesh);
382
- this.isLoaded = true;
383
-
384
- console.log('Fallback ์ ๊ธฐ ๋ชจ๋ธ ์ƒ์„ฑ ์™„๋ฃŒ');
385
- }
386
 
387
- update(playerPosition, deltaTime) {
388
- if (!this.mesh || !this.isLoaded) return;
389
-
390
- const currentTime = Date.now();
391
- const distanceToPlayer = this.position.distanceTo(playerPosition);
392
-
393
- // ๊ฐ„๋‹จํ•œ AI: ํ”Œ๋ ˆ์ด์–ด ์ถ”์ 
394
- if (distanceToPlayer < 5000) {
395
- const direction = new THREE.Vector3()
396
- .subVectors(playerPosition, this.position)
397
- .normalize();
398
-
399
- this.velocity = direction.multiplyScalar(this.speed);
400
- this.rotation.y = Math.atan2(direction.x, direction.z);
401
-
402
- // ๊ณต๊ฒฉ
403
- if (distanceToPlayer < 2000 && currentTime - this.lastShootTime > 1500) {
404
- this.shoot();
405
- }
406
- } else {
407
- // ์ˆœ์ฐฐ
408
- if (this.position.distanceTo(this.targetPosition) < 300) {
409
- const angle = Math.random() * Math.PI * 2;
410
- this.targetPosition = this.patrolCenter.clone().add(
411
- new THREE.Vector3(
412
- Math.cos(angle) * this.patrolRadius,
413
- (Math.random() - 0.5) * 1000,
414
- Math.sin(angle) * this.patrolRadius
415
- )
416
- );
417
- }
418
-
419
- const direction = new THREE.Vector3()
420
- .subVectors(this.targetPosition, this.position)
421
- .normalize();
422
-
423
- this.velocity = direction.multiplyScalar(this.speed * 0.7);
424
- this.rotation.y = Math.atan2(direction.x, direction.z);
425
- }
426
-
427
- // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
428
- this.position.add(this.velocity.clone().multiplyScalar(deltaTime));
429
-
430
- // ๊ณ ๋„ ์ œํ•œ
431
- if (this.position.y < GAME_CONSTANTS.MIN_ALTITUDE) {
432
- this.position.y = GAME_CONSTANTS.MIN_ALTITUDE;
433
- }
434
- if (this.position.y > GAME_CONSTANTS.MAX_ALTITUDE) {
435
- this.position.y = GAME_CONSTANTS.MAX_ALTITUDE;
436
  }
437
-
438
- // ๋งต ๊ฒฝ๊ณ„ ์ˆœํ™˜
439
- const mapLimit = GAME_CONSTANTS.MAP_SIZE / 2;
440
- if (this.position.x > mapLimit) this.position.x = -mapLimit;
441
- if (this.position.x < -mapLimit) this.position.x = mapLimit;
442
- if (this.position.z > mapLimit) this.position.z = -mapLimit;
443
- if (this.position.z < -mapLimit) this.position.z = mapLimit;
444
-
445
- // ๋ฉ”์‹œ ์—…๋ฐ์ดํŠธ
446
- this.mesh.position.copy(this.position);
447
- this.mesh.rotation.copy(this.rotation);
448
-
449
- // ์ด์•Œ ์—…๋ฐ์ดํŠธ
450
- this.updateBullets(deltaTime);
451
- }
452
-
453
- shoot() {
454
- this.lastShootTime = Date.now();
455
-
456
- const bulletGeometry = new THREE.SphereGeometry(0.15);
457
- const bulletMaterial = new THREE.MeshBasicMaterial({
458
- color: 0xff0000,
459
- emissive: 0xff0000,
460
- emissiveIntensity: 0.5
461
- });
462
- const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
463
-
464
- const muzzleOffset = new THREE.Vector3(0, 0, 6);
465
- muzzleOffset.applyEuler(this.rotation);
466
- bullet.position.copy(this.position).add(muzzleOffset);
467
-
468
- const direction = new THREE.Vector3(0, 0, 1);
469
- direction.applyEuler(this.rotation);
470
- bullet.velocity = direction.multiplyScalar(800);
471
-
472
- this.scene.add(bullet);
473
- this.bullets.push(bullet);
474
- }
475
 
476
- updateBullets(deltaTime) {
477
- for (let i = this.bullets.length - 1; i >= 0; i--) {
478
- const bullet = this.bullets[i];
479
- bullet.position.add(bullet.velocity.clone().multiplyScalar(deltaTime));
480
-
481
- if (bullet.position.distanceTo(this.position) > 5000 ||
482
- bullet.position.y < 0) {
483
- this.scene.remove(bullet);
484
- this.bullets.splice(i, 1);
485
- }
 
 
486
  }
487
- }
488
 
489
- takeDamage(damage) {
490
- this.health -= damage;
491
- return this.health <= 0;
492
- }
 
 
 
 
 
 
 
 
 
493
 
494
- destroy() {
495
- if (this.mesh) {
496
- this.scene.remove(this.mesh);
497
- this.bullets.forEach(bullet => this.scene.remove(bullet));
498
- this.bullets = [];
499
- this.isLoaded = false;
500
- }
501
- }
502
  }
503
 
504
- // ๋ฉ”์ธ ๊ฒŒ์ž„ ํด๋ž˜์Šค
505
- class Game {
506
- constructor() {
507
- this.scene = new THREE.Scene();
508
- this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 50000);
509
- this.renderer = new THREE.WebGLRenderer({ antialias: true });
510
- this.renderer.setSize(window.innerWidth, window.innerHeight);
511
- this.renderer.shadowMap.enabled = true;
512
- this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
513
- this.renderer.setClearColor(0x87CEEB);
514
- this.renderer.setPixelRatio(window.devicePixelRatio);
515
-
516
- document.getElementById('gameContainer').appendChild(this.renderer.domElement);
517
-
518
- this.loader = new GLTFLoader();
519
- this.fighter = new Fighter();
520
- this.enemies = [];
521
- this.isLoaded = false;
522
- this.isGameOver = false;
523
- this.gameTime = GAME_CONSTANTS.MISSION_DURATION;
524
- this.score = 0;
525
- this.lastTime = performance.now();
526
- this.gameTimer = null;
527
- this.animationFrameId = null;
528
-
529
- // ์˜ค๋””์˜ค ์‹œ์Šคํ…œ
530
- this.bgm = null;
531
- this.bgmPlaying = false;
532
-
533
- // ์ž…๋ ฅ ์ƒํƒœ
534
- this.keys = { w: false, a: false, s: false, d: false };
535
- this.isStarted = false;
536
-
537
- // ๊ธฐ๋ณธ ์ดˆ๊ธฐํ™”
538
- this.setupScene();
539
- this.setupEventListeners();
540
- this.preloadGame();
541
- }
542
 
543
- async preloadGame() {
544
- try {
545
- console.log('๊ฒŒ์ž„ ๋ฆฌ์†Œ์Šค ์‚ฌ์ „ ๋กœ๋”ฉ ์ค‘...');
546
-
547
- // ์ „ํˆฌ๊ธฐ ์‚ฌ์ „ ๋กœ๋”ฉ
548
- await this.fighter.initialize(this.scene, this.loader);
549
-
550
- if (!this.fighter.isLoaded) {
551
- throw new Error('์ „ํˆฌ๊ธฐ ๋กœ๋”ฉ ์‹คํŒจ');
552
- }
553
-
554
- // ์ดˆ๊ธฐ ์นด๋ฉ”๋ผ ์œ„์น˜ ์„ค์ •
555
- const initialCameraPos = this.fighter.getCameraPosition();
556
- this.camera.position.copy(initialCameraPos);
557
- this.camera.lookAt(this.fighter.position);
558
-
559
- // ์  ์ƒ์„ฑ
560
- await this.preloadEnemies();
561
-
562
- this.isLoaded = true;
563
- console.log('๊ฒŒ์ž„ ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ์™„๋ฃŒ');
564
-
565
- // ๋กœ๋”ฉ ํ™”๋ฉด ์ˆจ๊ธฐ๊ธฐ
566
- document.getElementById('loading').style.display = 'none';
567
-
568
- // ๋ Œ๋”๋ง ์‹œ์ž‘
569
- this.animate();
570
-
571
- } catch (error) {
572
- console.error('๊ฒŒ์ž„ ์‚ฌ์ „ ๋กœ๋”ฉ ์‹คํŒจ:', error);
573
- document.getElementById('loading').innerHTML =
574
- '<div class="loading-text" style="color: red;">๋กœ๋”ฉ ์‹คํŒจ. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”.</div>';
575
- }
576
- }
577
 
578
- async preloadEnemies() {
579
- for (let i = 0; i < GAME_CONSTANTS.ENEMY_COUNT; i++) {
580
- const angle = (i / GAME_CONSTANTS.ENEMY_COUNT) * Math.PI * 2;
581
- const distance = 6000 + Math.random() * 3000;
582
-
583
- const position = new THREE.Vector3(
584
- Math.cos(angle) * distance,
585
- 2500 + Math.random() * 2000,
586
- Math.sin(angle) * distance
587
- );
588
-
589
- const enemy = new EnemyFighter(this.scene, position);
590
- await enemy.initialize(this.loader);
591
- this.enemies.push(enemy);
592
- }
593
- }
594
 
595
- setupScene() {
596
- // ํ•˜๋Š˜ ๋ฐฐ๊ฒฝ
597
- this.scene.background = new THREE.Color(0x87CEEB);
598
- this.scene.fog = new THREE.Fog(0x87CEEB, 1000, 30000);
599
-
600
- // ์กฐ๋ช…
601
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
602
- this.scene.add(ambientLight);
603
-
604
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
605
- directionalLight.position.set(8000, 10000, 8000);
606
- directionalLight.castShadow = true;
607
- directionalLight.shadow.mapSize.width = 2048;
608
- directionalLight.shadow.mapSize.height = 2048;
609
- directionalLight.shadow.camera.near = 0.5;
610
- directionalLight.shadow.camera.far = 20000;
611
- directionalLight.shadow.camera.left = -10000;
612
- directionalLight.shadow.camera.right = 10000;
613
- directionalLight.shadow.camera.top = 10000;
614
- directionalLight.shadow.camera.bottom = -10000;
615
- this.scene.add(directionalLight);
616
-
617
- // ์ง€ํ˜•
618
- const groundGeometry = new THREE.PlaneGeometry(GAME_CONSTANTS.MAP_SIZE, GAME_CONSTANTS.MAP_SIZE);
619
- const groundMaterial = new THREE.MeshLambertMaterial({
620
- color: 0x8FBC8F,
621
- transparent: true,
622
- opacity: 0.8
623
- });
624
- const ground = new THREE.Mesh(groundGeometry, groundMaterial);
625
- ground.rotation.x = -Math.PI / 2;
626
- ground.receiveShadow = true;
627
- this.scene.add(ground);
628
-
629
- // ๊ตฌ๋ฆ„ ์ถ”๊ฐ€
630
- this.addClouds();
631
- }
632
 
633
- addClouds() {
634
- const cloudGeometry = new THREE.SphereGeometry(100, 8, 6);
635
- const cloudMaterial = new THREE.MeshLambertMaterial({
636
- color: 0xffffff,
637
- transparent: true,
638
- opacity: 0.5
639
- });
640
-
641
- for (let i = 0; i < 100; i++) {
642
- const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
643
- cloud.position.set(
644
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE,
645
- Math.random() * 4000 + 1000,
646
- (Math.random() - 0.5) * GAME_CONSTANTS.MAP_SIZE
647
- );
648
- cloud.scale.set(
649
- Math.random() * 3 + 1,
650
- Math.random() * 2 + 0.5,
651
- Math.random() * 3 + 1
652
- );
653
- this.scene.add(cloud);
654
  }
655
- }
656
-
657
- setupEventListeners() {
658
- // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ
659
- document.addEventListener('keydown', (event) => {
660
- if (!this.isStarted || this.isGameOver) return;
661
-
662
- switch(event.code) {
663
- case 'KeyW': this.keys.w = true; break;
664
- case 'KeyA': this.keys.a = true; break;
665
- case 'KeyS': this.keys.s = true; break;
666
- case 'KeyD': this.keys.d = true; break;
667
- }
668
- });
669
-
670
- document.addEventListener('keyup', (event) => {
671
- switch(event.code) {
672
- case 'KeyW': this.keys.w = false; break;
673
- case 'KeyA': this.keys.a = false; break;
674
- case 'KeyS': this.keys.s = false; break;
675
- case 'KeyD': this.keys.d = false; break;
676
- }
677
- });
678
 
679
- // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ
680
- document.addEventListener('mousemove', (event) => {
681
- if (!document.pointerLockElement || !this.isStarted || this.isGameOver) return;
682
-
683
- const deltaX = event.movementX || 0;
684
- const deltaY = event.movementY || 0;
685
-
686
- this.fighter.updateMouseInput(deltaX, deltaY);
687
- });
688
-
689
- document.addEventListener('click', () => {
690
- if (!this.isStarted) return;
691
-
692
- if (!document.pointerLockElement) {
693
- document.body.requestPointerLock();
694
- } else if (!this.isGameOver && this.fighter.isLoaded) {
695
- this.fighter.shoot(this.scene);
696
- }
697
- });
698
 
699
- // ์œˆ๋„์šฐ ๋ฆฌ์‚ฌ์ด์ฆˆ
700
- window.addEventListener('resize', () => {
701
- this.camera.aspect = window.innerWidth / window.innerHeight;
702
- this.camera.updateProjectionMatrix();
703
- this.renderer.setSize(window.innerWidth, window.innerHeight);
704
- });
705
- }
 
 
 
 
 
706
 
707
- startGame() {
708
- if (!this.isLoaded) {
709
- console.log('๊ฒŒ์ž„์ด ์•„์ง ๋กœ๋”ฉ ์ค‘์ž…๋‹ˆ๋‹ค...');
710
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711
  }
712
 
713
- this.isStarted = true;
714
- this.startBGM();
715
- this.startGameTimer();
716
-
717
- console.log('๊ฒŒ์ž„ ์‹œ์ž‘!');
718
- }
719
-
720
- startBGM() {
721
- if (this.bgmPlaying) return;
 
 
722
 
723
- try {
724
- this.bgm = new Audio('sounds/main_bgm.ogg');
725
- this.bgm.volume = 0.5;
726
- this.bgm.loop = true;
727
- this.bgm.play();
728
- this.bgmPlaying = true;
729
- console.log('BGM ์žฌ์ƒ ์‹œ์ž‘');
730
- } catch (error) {
731
- console.log('BGM ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:', error);
 
 
732
  }
733
- }
734
-
735
- startGameTimer() {
736
- this.gameTimer = setInterval(() => {
737
- if (!this.isGameOver) {
738
- this.gameTime--;
739
-
740
- if (this.gameTime <= 0) {
741
- this.endGame(true);
742
- }
743
- }
744
- }, 1000);
745
- }
746
-
747
- updateUI() {
748
- if (this.fighter.isLoaded) {
749
- const speedKnots = Math.round(this.fighter.speed * 1.94384);
750
- const altitudeFeet = Math.round(this.fighter.altitude * 3.28084);
751
-
752
- document.getElementById('score').textContent = `Score: ${this.score}`;
753
- document.getElementById('time').textContent = `Time: ${this.gameTime}s`;
754
- document.getElementById('health').style.width = `${this.fighter.health}%`;
755
- document.getElementById('ammoDisplay').textContent = `AMMO: ${this.fighter.ammo}`;
756
-
757
- const gameStats = document.getElementById('gameStats');
758
- if (gameStats) {
759
- gameStats.innerHTML = `
760
- <div>Score: ${this.score}</div>
761
- <div>Time: ${this.gameTime}s</div>
762
- <div>Speed: ${speedKnots} KT</div>
763
- <div>Alt: ${altitudeFeet} FT</div>
764
- <div>Throttle: ${Math.round(this.fighter.throttle * 100)}%</div>
765
- <div>G-Force: ${this.fighter.gForce.toFixed(1)}</div>
766
- <div>Targets: ${this.enemies.length}</div>
767
- `;
768
- }
769
  }
770
- }
771
-
772
- updateRadar() {
773
- const radar = document.getElementById('radar');
774
- if (!radar) return;
775
 
776
- const oldDots = radar.getElementsByClassName('enemy-dot');
777
- while (oldDots[0]) {
778
- oldDots[0].remove();
 
 
 
 
 
 
779
  }
780
 
781
- const radarCenter = { x: 100, y: 100 };
782
- const radarRange = 10000;
 
 
 
 
 
 
783
 
784
- this.enemies.forEach(enemy => {
785
- if (!enemy.mesh || !enemy.isLoaded) return;
786
-
787
- const distance = this.fighter.position.distanceTo(enemy.position);
788
- if (distance <= radarRange) {
789
- const relativePos = enemy.position.clone().sub(this.fighter.position);
790
- const angle = Math.atan2(relativePos.x, relativePos.z);
791
- const relativeDistance = distance / radarRange;
792
-
793
- const dotX = radarCenter.x + Math.sin(angle) * (radarCenter.x * relativeDistance);
794
- const dotY = radarCenter.y + Math.cos(angle) * (radarCenter.y * relativeDistance);
795
-
796
- const dot = document.createElement('div');
797
- dot.className = 'enemy-dot';
798
- dot.style.left = `${dotX}px`;
799
- dot.style.top = `${dotY}px`;
800
- radar.appendChild(dot);
801
  }
802
- });
803
- }
804
-
805
- checkCollisions() {
806
- // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ๊ณผ ์  ์ถฉ๋Œ
807
- for (let i = this.fighter.bullets.length - 1; i >= 0; i--) {
808
- const bullet = this.fighter.bullets[i];
809
-
810
- for (let j = this.enemies.length - 1; j >= 0; j--) {
811
- const enemy = this.enemies[j];
812
- if (!enemy.mesh || !enemy.isLoaded) continue;
813
-
814
- const distance = bullet.position.distanceTo(enemy.position);
815
- if (distance < 25) {
816
- this.scene.remove(bullet);
817
- this.fighter.bullets.splice(i, 1);
818
-
819
- if (enemy.takeDamage(30)) {
820
- enemy.destroy();
821
- this.enemies.splice(j, 1);
822
- this.score += 100;
823
- }
824
- break;
825
- }
826
  }
827
  }
828
-
829
- // ์  ์ด์•Œ๊ณผ ํ”Œ๋ ˆ์ด์–ด ์ถฉ๋Œ
830
- this.enemies.forEach(enemy => {
831
- enemy.bullets.forEach((bullet, index) => {
832
- const distance = bullet.position.distanceTo(this.fighter.position);
833
- if (distance < 30) {
834
- this.scene.remove(bullet);
835
- enemy.bullets.splice(index, 1);
836
-
837
- if (this.fighter.takeDamage(20)) {
838
- this.endGame(false);
839
- }
840
- }
841
- });
842
- });
843
- }
844
 
845
- animate() {
846
- if (this.isGameOver) return;
847
-
848
- this.animationFrameId = requestAnimationFrame(() => this.animate());
849
-
850
- const currentTime = performance.now();
851
- const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
852
- this.lastTime = currentTime;
853
-
854
- if (this.isLoaded && this.fighter.isLoaded) {
855
- if (this.isStarted) {
856
- // ์ „ํˆฌ๊ธฐ ์—…๋ฐ์ดํŠธ
857
- this.fighter.updateControls(this.keys, deltaTime);
858
- this.fighter.updatePhysics(deltaTime);
859
- this.fighter.updateBullets(this.scene, deltaTime);
860
-
861
- // ์  ์—…๋ฐ์ดํŠธ
862
- this.enemies.forEach(enemy => {
863
- enemy.update(this.fighter.position, deltaTime);
864
- });
865
-
866
- // ์ถฉ๋Œ ๊ฒ€์‚ฌ
867
- this.checkCollisions();
868
-
869
- // UI ์—…๋ฐ์ดํŠธ
870
- this.updateUI();
871
- this.updateRadar();
872
-
873
- // ์Šน๋ฆฌ ์กฐ๊ฑด ํ™•์ธ
874
- if (this.enemies.length === 0) {
875
- this.endGame(true);
876
- }
877
- }
878
-
879
- // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ (๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋”ฐ๋ผ๊ฐ€๊ธฐ)
880
- const targetCameraPos = this.fighter.getCameraPosition();
881
- const targetCameraTarget = this.fighter.getCameraTarget();
882
-
883
- this.camera.position.lerp(targetCameraPos, this.fighter.cameraLag);
884
- this.camera.lookAt(targetCameraTarget);
885
  }
886
-
887
- this.renderer.render(this.scene, this.camera);
888
- }
889
 
890
- endGame(victory = false) {
891
- this.isGameOver = true;
892
-
893
- if (this.bgm) {
894
- this.bgm.pause();
895
- this.bgm = null;
896
- this.bgmPlaying = false;
 
 
 
 
 
897
  }
898
-
899
- if (this.gameTimer) {
900
- clearInterval(this.gameTimer);
 
 
 
 
 
 
 
 
901
  }
902
 
903
- document.exitPointerLock();
904
-
905
- const gameOverDiv = document.createElement('div');
906
- gameOverDiv.className = 'start-screen';
907
- gameOverDiv.innerHTML = `
908
- <h1 style="color: ${victory ? '#0f0' : '#f00'}; font-size: 48px;">
909
- ${victory ? 'MISSION ACCOMPLISHED!' : 'SHOT DOWN!'}
910
- </h1>
911
- <div style="color: #0f0; font-size: 24px; margin: 20px 0;">
912
- Final Score: ${this.score}<br>
913
- Enemies Destroyed: ${GAME_CONSTANTS.ENEMY_COUNT - this.enemies.length}<br>
914
- Mission Time: ${GAME_CONSTANTS.MISSION_DURATION - this.gameTime}s
915
- </div>
916
- <button class="start-button" onclick="location.reload()">
917
- New Mission
918
- </button>
919
- `;
920
-
921
- document.body.appendChild(gameOverDiv);
922
- }
923
- }
924
 
925
- // ๊ฒŒ์ž„ ์ธ์Šคํ„ด์Šค
926
- let gameInstance = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
 
928
- // ์ „์—ญ ํ•จ์ˆ˜
929
- window.startGame = function() {
930
- document.getElementById('startScreen').style.display = 'none';
931
- document.body.requestPointerLock();
932
 
933
- if (gameInstance && gameInstance.isLoaded) {
934
- gameInstance.startGame();
935
- } else {
936
- console.log('๊ฒŒ์ž„์ด ์•„์ง ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค...');
937
- }
938
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
 
940
- // ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
941
- document.addEventListener('DOMContentLoaded', () => {
942
- console.log('์ „ํˆฌ๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ดˆ๊ธฐํ™” ์ค‘...');
943
- gameInstance = new Game();
944
- });
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r17/Stats.min.js"></script>
7
+ <title>JET FIGHT SIMULATER - FPS Mode</title>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ overflow: hidden;
12
+ background: #000;
13
+ font-family: 'Courier New', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
+ #enemyLabels {
16
+ position: absolute;
17
+ top: 0;
18
+ left: 0;
19
+ width: 100%;
20
+ height: 100%;
21
+ pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
24
+ .enemy-label {
25
+ position: absolute;
26
+ background-color: rgba(255, 0, 0, 0.7);
27
+ color: white;
28
+ padding: 2px 6px;
29
+ border-radius: 3px;
30
+ font-size: 12px;
31
+ font-family: Arial, sans-serif;
32
+ transform: translate(-50%, -50%);
33
+ white-space: nowrap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
+ #loading {
36
+ position: fixed;
37
+ top: 50%;
38
+ left: 50%;
39
+ transform: translate(-50%, -50%);
40
+ background: rgba(0,0,0,0.8);
41
+ padding: 20px;
42
+ border-radius: 10px;
43
+ z-index: 2000;
44
+ text-align: center;
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ .loading-spinner {
48
+ width: 50px;
49
+ height: 50px;
50
+ border: 5px solid #0f0;
51
+ border-top: 5px solid transparent;
52
+ border-radius: 50%;
53
+ animation: spin 1s linear infinite;
54
+ margin: 0 auto 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
 
56
 
57
+ @keyframes spin {
58
+ 0% { transform: rotate(0deg); }
59
+ 100% { transform: rotate(360deg); }
 
 
 
 
 
 
 
 
 
 
60
  }
 
61
 
62
+ .loading-text {
63
+ color: #0f0;
64
+ font-size: 24px;
65
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ #gameContainer {
69
+ position: relative;
70
+ width: 100vw;
71
+ height: 100vh;
72
+ cursor: crosshair;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
+ #info {
76
+ position: absolute;
77
+ top: 10px;
78
+ left: 10px;
79
+ color: #0f0;
80
+ background: rgba(0,20,0,0.7);
81
+ padding: 10px;
82
+ font-size: 14px;
83
+ z-index: 1001;
84
+ border: 1px solid #0f0;
85
+ border-radius: 5px;
86
+ user-select: none;
87
  }
 
88
 
89
+ #crosshair {
90
+ position: fixed;
91
+ top: 25%;
92
+ left: 50%;
93
+ transform: translate(-50%, -50%);
94
+ width: 40px;
95
+ height: 40px;
96
+ border: 2px solid #00ff00;
97
+ border-radius: 50%;
98
+ z-index: 1001;
99
+ pointer-events: none;
100
+ transition: all 0.2s ease;
101
+ }
102
 
103
+ /* ๋ฌผ์ฒด๊ฐ€ ๊ฐ์ง€๋˜์—ˆ์„ ๋•Œ์˜ ์Šคํƒ€์ผ */
104
+ #crosshair.target-detected {
105
+ border-color: #ff0000;
106
+ border-width: 3px;
107
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.5);
 
 
 
108
  }
109
 
110
+ #crosshair::before,
111
+ #crosshair::after {
112
+ content: '';
113
+ position: absolute;
114
+ background: #00ff00;
115
+ transition: all 0.2s ease;
116
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ #crosshair::before {
119
+ top: 50%;
120
+ left: -10px;
121
+ right: -10px;
122
+ height: 2px;
123
+ transform: translateY(-50%);
124
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
+ #crosshair::after {
127
+ left: 50%;
128
+ top: -10px;
129
+ bottom: -10px;
130
+ width: 2px;
131
+ transform: translateX(-50%);
132
+ }
 
 
 
 
 
 
 
 
 
133
 
134
+ /* ๋ฌผ์ฒด๊ฐ€ ๊ฐ์ง€๋˜์—ˆ์„ ๋•Œ์˜ ํฌ๋กœ์Šค ๋ผ์ธ ์Šคํƒ€์ผ */
135
+ #crosshair.target-detected::before,
136
+ #crosshair.target-detected::after {
137
+ background: #ff0000;
138
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ #healthBar {
141
+ position: absolute;
142
+ bottom: 20px;
143
+ left: 20px;
144
+ width: 200px;
145
+ height: 20px;
146
+ background: rgba(0,20,0,0.7);
147
+ border: 2px solid #0f0;
148
+ z-index: 1001;
149
+ border-radius: 10px;
150
+ overflow: hidden;
 
 
 
 
 
 
 
 
 
 
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ #health {
154
+ width: 100%;
155
+ height: 100%;
156
+ background: linear-gradient(90deg, #0f0, #00ff00);
157
+ transition: width 0.3s;
158
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ #ammo {
161
+ position: absolute;
162
+ bottom: 20px;
163
+ right: 20px;
164
+ color: #0f0;
165
+ background: rgba(0,20,0,0.7);
166
+ padding: 10px;
167
+ font-size: 20px;
168
+ z-index: 1001;
169
+ border: 1px solid #0f0;
170
+ border-radius: 5px;
171
+ }
172
 
173
+
174
+ #gameTitle {
175
+ position: absolute;
176
+ top: 10px;
177
+ left: 50%;
178
+ transform: translateX(-50%);
179
+ color: #0f0;
180
+ background: rgba(0,20,0,0.7);
181
+ padding: 10px 20px;
182
+ font-size: 20px;
183
+ z-index: 1001;
184
+ border: 1px solid #0f0;
185
+ border-radius: 5px;
186
+ text-transform: uppercase;
187
+ letter-spacing: 2px;
188
+ }
189
+ #ammoDisplay {
190
+ position: absolute;
191
+ bottom: 20px;
192
+ right: 20px;
193
+ color: #0f0;
194
+ background: rgba(0,20,0,0.7);
195
+ padding: 10px;
196
+ font-size: 20px;
197
+ z-index: 1001;
198
+ border: 1px solid #0f0;
199
+ border-radius: 5px;
200
  }
201
 
202
+ #reloadingText {
203
+ position: absolute;
204
+ top: 50%;
205
+ left: 50%;
206
+ transform: translate(-50%, -50%);
207
+ color: #ff0000;
208
+ font-size: 24px;
209
+ font-weight: bold;
210
+ display: none;
211
+ z-index: 1002;
212
+ }
213
 
214
+ #radar {
215
+ position: absolute;
216
+ bottom: 60px; /* ์ฒด๋ ฅ๋ฐ” ์œ„๋กœ ์ด๋™ */
217
+ left: 20px;
218
+ width: 200px;
219
+ height: 200px;
220
+ background: rgba(0,20,0,0.3);
221
+ border: 2px solid #0f0;
222
+ border-radius: 50%;
223
+ z-index: 1001;
224
+ overflow: hidden;
225
  }
226
+
227
+ #mission {
228
+ position: absolute;
229
+ top: 10px;
230
+ left: 10px;
231
+ color: #0f0;
232
+ background: rgba(0,20,0,0.7);
233
+ padding: 10px;
234
+ font-size: 16px;
235
+ z-index: 1001;
236
+ border: 1px solid #0f0;
237
+ border-radius: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
 
 
 
 
 
239
 
240
+ #radarLine {
241
+ position: absolute;
242
+ top: 50%;
243
+ left: 50%;
244
+ width: 50%;
245
+ height: 2px;
246
+ background: #0f0;
247
+ transform-origin: left center;
248
+ animation: radar-sweep 4s infinite linear;
249
  }
250
 
251
+ .enemy-dot {
252
+ position: absolute;
253
+ width: 6px;
254
+ height: 6px;
255
+ background: #ff0000;
256
+ border-radius: 50%;
257
+ transform: translate(-50%, -50%);
258
+ }
259
 
260
+ @keyframes radar-sweep {
261
+ from {
262
+ transform: rotate(0deg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
+ to {
265
+ transform: rotate(360deg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
+ #gameStats {
270
+ position: absolute;
271
+ top: 10px;
272
+ right: 20px;
273
+ color: #0f0;
274
+ background: rgba(0,20,0,0.7);
275
+ padding: 10px;
276
+ font-size: 16px;
277
+ z-index: 1001;
278
+ border: 1px solid #0f0;
279
+ border-radius: 5px;
280
+ text-align: right;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  }
 
 
 
282
 
283
+ .start-screen {
284
+ position: fixed;
285
+ top: 0;
286
+ left: 0;
287
+ width: 100%;
288
+ height: 100%;
289
+ background: rgba(0,0,0,0.8);
290
+ display: flex;
291
+ justify-content: center;
292
+ align-items: center;
293
+ flex-direction: column;
294
+ z-index: 2000;
295
  }
296
+
297
+ .start-button {
298
+ padding: 15px 30px;
299
+ font-size: 24px;
300
+ background: #0f0;
301
+ color: #000;
302
+ border: none;
303
+ border-radius: 5px;
304
+ cursor: pointer;
305
+ margin-top: 20px;
306
+ transition: transform 0.2s;
307
  }
308
 
309
+ .start-button:hover {
310
+ transform: scale(1.1);
311
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
+ #minimap {
314
+ position: absolute;
315
+ bottom: 20px;
316
+ right: 20px;
317
+ width: 200px;
318
+ height: 200px;
319
+ background: rgba(0,20,0,0.7);
320
+ border: 2px solid #0f0;
321
+ border-radius: 5px;
322
+ z-index: 1001;
323
+ }
324
+ </style>
325
+ </head>
326
+ <body>
327
+ <div id="loading">
328
+ <div class="loading-spinner"></div>
329
+ <div class="loading-text">Loading tank assets...</div>
330
+ </div>
331
+
332
+ <div class="start-screen" id="startScreen">
333
+ <h1 style="color: #0f0; font-size: 48px; margin-bottom: 20px;">JET FIGHT SIMULATER</h1>
334
+ <button class="start-button" onclick="startGame()">Start Game</button>
335
+ <div style="color: #0f0; margin-top: 20px; text-align: center;">
336
+ <p>Controls:</p>
337
+ <p>W,A,S,D - Move Tank</p>
338
+ <p>Mouse - Look Around</p>
339
+ <p>Left Click - Fire</p>
340
+ <p>ESC - Pause</p>
341
+ </div>
342
+ </div>
343
 
 
 
 
 
344
 
345
+ <div id="gameContainer">
346
+ <div id="gameTitle">JET FIGHT SIMULATER</div>
347
+ <div id="mission">MISSION: DESTROY ENEMY JET</div>
348
+ <div id="gameStats">
349
+ <div id="score">Score: 0</div>
350
+ <div id="time">Time: 180s</div>
351
+ </div>
352
+ <div id="crosshair"></div>
353
+ <div id="detected" style="position: absolute; top: 55%; left: 50%; transform: translate(-50%, -50%); color: red; font-size: 16px; display: none;">DETECTED</div>
354
+ <div id="enemyLabels"></div>
355
+
356
+ <!-- ์ฒด๋ ฅ๋ฐ” -->
357
+ <div id="healthBar">
358
+ <div id="health"></div>
359
+ </div>
360
+
361
+ <!-- ์ƒˆ๋กœ์šด ํƒ„์•ฝ ์‹œ์Šคํ…œ -->
362
+ <div id="ammoDisplay">APFSDS: 1/1</div>
363
+
364
+ <!-- ๋ฆฌ๋กœ๋”ฉ ํ…์ŠคํŠธ -->
365
+ <div id="reloadingText">RELOADING...</div>
366
+
367
+ <!-- ๋ ˆ์ด๋” -->
368
+ <div id="radar">
369
+ <div id="radarLine"></div>
370
+ </div>
371
+ </div>
372
+
373
+ <script type="importmap">
374
+ {
375
+ "imports": {
376
+ "three": "https://unpkg.com/[email protected]/build/three.module.js",
377
+ "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
378
+ }
379
+ }
380
+ </script>
381
+ <script>
382
+ function startGame() {
383
+ document.getElementById('startScreen').style.display = 'none';
384
+ // ์—ฌ๊ธฐ์— ๊ฒŒ์ž„ ์‹œ์ž‘ ๋กœ์ง ์ถ”๊ฐ€
385
+ document.body.requestPointerLock();
386
+ }
387
 
388
+ // ํฌ์ธํ„ฐ ๋ฝ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
389
+ document.addEventListener('pointerlockchange', () => {
390
+ if (document.pointerLockElement === document.body) {
391
+ console.log('Pointer locked');
392
+ } else {
393
+ console.log('Pointer unlocked');
394
+ }
395
+ });
396
+ </script>
397
+ <script type="module" src="game.js"></script>
398
+ </body>
399
+ </html>