cutechicken commited on
Commit
253e4b2
โ€ข
1 Parent(s): 57a9965

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +221 -343
game.js CHANGED
@@ -330,7 +330,7 @@ class Game {
330
  this.disposedObjects = new Set(); // ์ œ๊ฑฐ๋œ ๊ฐ์ฒด ์ถ”์ 
331
  }
332
 
333
- // ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
334
  dispose(object) {
335
  if (this.disposedObjects.has(object)) return;
336
 
@@ -345,7 +345,8 @@ class Game {
345
  if (object.parent) object.parent.remove(object);
346
  this.disposedObjects.add(object);
347
  }
348
- // ์ด์•Œ ์ œ๊ฑฐ ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
 
349
  cleanupBullets() {
350
  // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ ์ •๋ฆฌ
351
  for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
@@ -356,7 +357,8 @@ class Game {
356
  this.tank.bullets.splice(i, 1);
357
  }
358
  }
359
- // ์  ์ด์•Œ ์ •๋ฆฌ
 
360
  this.enemies.forEach(enemy => {
361
  for (let i = enemy.bullets.length - 1; i >= 0; i--) {
362
  const bullet = enemy.bullets[i];
@@ -368,394 +370,267 @@ class Game {
368
  }
369
  });
370
  }
371
- }
372
-
373
 
374
  async initialize() {
375
- try {
376
- // ์กฐ๋ช… ์„ค์ •
377
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
378
- this.scene.add(ambientLight);
379
-
380
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
381
- directionalLight.position.set(50, 50, 50);
382
- directionalLight.castShadow = true;
383
- directionalLight.shadow.mapSize.width = 2048;
384
- directionalLight.shadow.mapSize.height = 2048;
385
- this.scene.add(directionalLight);
386
-
387
- // ์ง€ํ˜• ์ƒ์„ฑ
388
- const ground = new THREE.Mesh(
389
- new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE),
390
- new THREE.MeshStandardMaterial({
391
- color: 0x333333,
392
- roughness: 0.9,
393
- metalness: 0.1
394
- })
395
- );
396
- ground.rotation.x = -Math.PI / 2;
397
- ground.receiveShadow = true;
398
- this.scene.add(ground);
399
-
400
- // ๊ฑด๋ฌผ ์ƒ์„ฑ
401
- await this.createBuildings();
402
-
403
- // ํƒฑํฌ ์ดˆ๊ธฐํ™”
404
- await this.tank.initialize(this.scene, this.loader);
405
- if (!this.tank.isLoaded) {
406
- throw new Error('Tank loading failed');
407
- }
408
 
409
- // ์นด๋ฉ”๋ผ ์„ค์ • ์ˆ˜์ •
410
- // ํƒฑํฌ์˜ ํ˜„์žฌ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ
411
- const tankPosition = this.tank.getPosition();
412
- // ์นด๋ฉ”๋ผ๋ฅผ ํƒฑํฌ ์œ„์น˜ ๊ธฐ์ค€์œผ๋กœ ์„ค์ •
413
- this.camera.position.set(
414
- tankPosition.x,
415
- tankPosition.y + 15, // ํƒฑํฌ๋ณด๋‹ค 15 ์œ ๋‹› ์œ„์—
416
- tankPosition.z - 30 // ํƒฑํฌ๋ณด๋‹ค 30 ์œ ๋‹› ๋’ค์—
417
- );
418
- // ์นด๋ฉ”๋ผ๊ฐ€ ํƒฑํฌ๋ฅผ ๋ฐ”๋ผ๋ณด๋„๋ก ์„ค์ •
419
- this.camera.lookAt(new THREE.Vector3(
420
- tankPosition.x,
421
- tankPosition.y + 2, // ํƒฑํฌ์˜ ์ƒ๋‹จ ๋ถ€๋ถ„์„ ๋ฐ”๋ผ๋ณด๋„๋ก
422
- tankPosition.z
423
- ));
424
-
425
- // ๋กœ๋”ฉ ์™„๋ฃŒ
426
- this.isLoading = false;
427
- document.getElementById('loading').style.display = 'none';
428
-
429
- // ๊ฒŒ์ž„ ์‹œ์ž‘
430
- this.animate();
431
- this.spawnEnemies();
432
- this.startGameTimer();
433
-
434
- } catch (error) {
435
- console.error('Game initialization error:', error);
436
- this.handleLoadingError();
437
- }
438
- }
439
 
440
- setupEventListeners() {
441
- // ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€
442
- document.addEventListener('keydown', (event) => {
443
- if (this.isLoading) return;
444
- switch(event.code) {
445
- case 'KeyW': this.keys.forward = true; break;
446
- case 'KeyS': this.keys.backward = true; break;
447
- case 'KeyA': this.keys.left = true; break;
448
- case 'KeyD': this.keys.right = true; break;
449
- }
450
- });
451
 
452
- document.addEventListener('keyup', (event) => {
453
- if (this.isLoading) return;
454
- switch(event.code) {
455
- case 'KeyW': this.keys.forward = false; break;
456
- case 'KeyS': this.keys.backward = false; break;
457
- case 'KeyA': this.keys.left = false; break;
458
- case 'KeyD': this.keys.right = false; break;
459
- }
460
- });
 
 
 
461
 
462
- // ๋งˆ์šฐ์Šค ์›€์ง์ž„ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์ •
463
- document.addEventListener('mousemove', (event) => {
464
- if (this.isLoading || !document.pointerLockElement) return;
465
-
466
- // movementX/Y๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งˆ์šฐ์Šค ํšŒ์ „ ๊ณ„์‚ฐ
467
- this.mouse.x += event.movementX * 0.002;
468
- this.mouse.y += event.movementY * 0.002;
469
- });
470
 
471
- // ํด๋ฆญ ์ด๋ฒคํŠธ ์ˆ˜์ • - ํฌ์ธํ„ฐ ๋ฝ๊ณผ ๋ฐœ์‚ฌ๋ฅผ ํ•จ๊ป˜ ์ฒ˜๋ฆฌ
472
- document.addEventListener('click', () => {
473
- if (!document.pointerLockElement) {
474
- document.body.requestPointerLock();
475
- } else {
476
- const bullet = this.tank.shoot(this.scene);
477
- if (bullet) {
478
- // ์ด์•Œ ๋ฐœ์‚ฌ ํšจ๊ณผ์Œ์ด๋‚˜ ์‹œ๊ฐํšจ๊ณผ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
479
- }
480
- }
481
- });
482
 
483
- // ํฌ์ธํ„ฐ ๋ฝ ์ƒํƒœ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ์ถ”๊ฐ€
484
- document.addEventListener('pointerlockchange', () => {
485
- if (!document.pointerLockElement) {
486
- // ํฌ์ธํ„ฐ ๋ฝ์ด ํ•ด์ œ๋˜์—ˆ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ
487
- this.mouse.x = 0;
488
- this.mouse.y = 0;
489
  }
490
- });
491
-
492
- // ์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€
493
- window.addEventListener('resize', () => {
494
- this.camera.aspect = window.innerWidth / window.innerHeight;
495
- this.camera.updateProjectionMatrix();
496
- this.renderer.setSize(window.innerWidth, window.innerHeight);
497
- });
498
- }
499
- handleMovement() {
500
- if (!this.tank.isLoaded) return;
501
-
502
- const direction = new THREE.Vector3();
503
-
504
- if (this.keys.forward) direction.z += 1;
505
- if (this.keys.backward) direction.z -= 1;
506
- if (this.keys.left) direction.x -= 1;
507
- if (this.keys.right) direction.x += 1;
508
-
509
- if (direction.length() > 0) {
510
- direction.normalize();
511
-
512
- // A,D ํ‚ค๋กœ ํƒฑํฌ ํšŒ์ „
513
- if (this.keys.left) this.tank.rotate(-1);
514
- if (this.keys.right) this.tank.rotate(1);
515
-
516
- // ํ˜„์žฌ ํƒฑํฌ์˜ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™
517
- direction.applyEuler(this.tank.body.rotation);
518
- this.tank.move(direction);
519
  }
520
 
521
- // ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ํƒฑํฌ ๊ธฐ์ค€์œผ๋กœ ๋ณ€ํ™˜
522
- const mouseVector = new THREE.Vector2(this.mouse.x, -this.mouse.y);
523
- const rotationAngle = Math.atan2(mouseVector.x, mouseVector.y);
524
-
525
- // ํฌํƒ‘ ํšŒ์ „
526
- if (this.tank.turretGroup) {
527
- this.tank.turretGroup.rotation.y = rotationAngle;
528
- }
 
 
529
 
530
- // ์—ฌ๊ธฐ๋ถ€ํ„ฐ ์นด๋ฉ”๋ผ ๋กœ์ง์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค
531
- const tankPos = this.tank.getPosition();
532
- const cameraDistance = 30; // ์นด๋ฉ”๋ผ์™€ ํƒฑํฌ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ
533
- const cameraHeight = 15; // ์นด๋ฉ”๋ผ์˜ ๋†’์ด
534
- const lookAtHeight = 5; // ์นด๋ฉ”๋ผ๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ๋†’์ด
535
-
536
- // ํƒฑํฌ์˜ ํšŒ์ „์— ๋”ฐ๋ผ ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ
537
- const tankRotation = this.tank.body.rotation.y;
538
-
539
- // ์นด๋ฉ”๋ผ ์œ„์น˜ ๊ณ„์‚ฐ ์ˆ˜์ •
540
- this.camera.position.set(
541
- tankPos.x - Math.sin(tankRotation) * cameraDistance,
542
- tankPos.y + cameraHeight,
543
- tankPos.z - Math.cos(tankRotation) * cameraDistance
544
- );
545
-
546
- // ์นด๋ฉ”๋ผ๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ์ง€์ ์„ ํƒฑํฌ ์œ„์น˜๋ณด๋‹ค ์•ฝ๊ฐ„ ์•ž์ชฝ์œผ๋กœ ์„ค์ •
547
- const lookAtPoint = new THREE.Vector3(
548
- tankPos.x + Math.sin(tankRotation) * 10, // ํƒฑํฌ ์•ž์ชฝ 10 ์œ ๋‹›
549
- tankPos.y + lookAtHeight, // ํƒฑํฌ๋ณด๋‹ค ์•ฝ๊ฐ„ ์œ„
550
- tankPos.z + Math.cos(tankRotation) * 10 // ํƒฑํฌ ์•ž์ชฝ 10 ์œ ๋‹›
551
- );
552
-
553
- this.camera.lookAt(lookAtPoint);
554
- }
555
- createBuildings() {
556
- const buildingTypes = [
557
- { width: 10, height: 30, depth: 10, color: 0x808080 },
558
- { width: 15, height: 40, depth: 15, color: 0x606060 },
559
- { width: 20, height: 50, depth: 20, color: 0x404040 }
560
- ];
561
-
562
- for (let i = 0; i < BUILDING_COUNT; i++) {
563
- const type = buildingTypes[Math.floor(Math.random() * buildingTypes.length)];
564
- const building = this.createBuilding(type);
565
-
566
- let position;
567
- let attempts = 0;
568
- do {
569
- position = new THREE.Vector3(
570
- (Math.random() - 0.5) * (MAP_SIZE - type.width),
571
- type.height / 2,
572
- (Math.random() - 0.5) * (MAP_SIZE - type.depth)
573
- );
574
- attempts++;
575
- } while (this.checkBuildingCollision(position, type) && attempts < 50);
576
-
577
- if (attempts < 50) {
578
- building.position.copy(position);
579
- this.buildings.push(building);
580
- this.scene.add(building);
581
  }
582
- }
583
- }
584
 
585
- createBuilding(type) {
586
- const geometry = new THREE.BoxGeometry(type.width, type.height, type.depth);
587
- const material = new THREE.MeshPhongMaterial({
588
- color: type.color,
589
- emissive: 0x222222,
590
- specular: 0x111111,
591
- shininess: 30
592
  });
593
- const building = new THREE.Mesh(geometry, material);
594
- building.castShadow = true;
595
- building.receiveShadow = true;
596
- return building;
597
- }
598
 
599
- checkBuildingCollision(position, type) {
600
- const margin = 5;
601
- const bbox = new THREE.Box3(
602
- new THREE.Vector3(
603
- position.x - (type.width / 2 + margin),
604
- 0,
605
- position.z - (type.depth / 2 + margin)
606
- ),
607
- new THREE.Vector3(
608
- position.x + (type.width / 2 + margin),
609
- type.height,
610
- position.z + (type.depth / 2 + margin)
611
- )
612
- );
613
 
614
- return this.buildings.some(building => {
615
- const buildingBox = new THREE.Box3().setFromObject(building);
616
- return bbox.intersectsBox(buildingBox);
 
 
617
  });
618
- }
619
 
620
- handleLoadingError() {
621
- this.isLoading = false;
622
- const loadingElement = document.getElementById('loading');
623
- if (loadingElement) {
624
- loadingElement.innerHTML = `
625
- <div class="loading-text" style="color: red;">
626
- Loading failed. Please refresh the page.
627
- </div>
628
- `;
629
- }
630
  }
631
 
632
- spawnEnemies() {
633
- let spawnTimeout;
634
-
635
- const spawnEnemy = () => {
636
- if (this.isGameOver) {
637
- clearTimeout(spawnTimeout);
638
- return;
 
 
 
 
 
 
 
 
 
 
 
639
  }
640
 
641
- if (this.enemies.length < ENEMY_COUNT_MAX) {
642
- const position = this.getValidEnemySpawnPosition();
643
- if (position) {
644
- const type = Math.random() < 0.7 ? 'tank' : 'heavy';
645
- const enemy = new Enemy(this.scene, position, type);
646
- enemy.initialize(this.loader).catch(console.error);
647
- this.enemies.push(enemy);
648
- }
649
  }
 
 
 
 
 
650
 
651
- spawnTimeout = setTimeout(spawnEnemy, 3000);
652
- };
653
-
654
- spawnEnemy();
655
- }
 
 
656
 
657
- getValidEnemySpawnPosition() {
658
- const margin = 20;
659
- let position;
660
- let attempts = 0;
661
- const maxAttempts = 50;
662
-
663
- do {
664
- position = new THREE.Vector3(
665
- (Math.random() - 0.5) * (MAP_SIZE - margin * 2),
666
- ENEMY_GROUND_HEIGHT,
667
- (Math.random() - 0.5) * (MAP_SIZE - margin * 2)
668
- );
669
 
670
- const distanceToPlayer = position.distanceTo(this.tank.getPosition());
671
- if (distanceToPlayer < 100) continue;
672
 
673
- let collisionFound = false;
674
- for (const building of this.buildings) {
675
- const buildingBox = new THREE.Box3().setFromObject(building);
676
- if (buildingBox.containsPoint(position)) {
677
- collisionFound = true;
678
- break;
679
- }
680
- }
681
 
682
- if (!collisionFound) return position;
 
 
683
 
684
- attempts++;
685
- } while (attempts < maxAttempts);
 
 
686
 
687
- return null;
688
- }
689
 
690
- startGameTimer() {
691
- const timer = setInterval(() => {
692
- if (this.isLoading) return;
 
 
 
 
693
 
694
- this.gameTime--;
695
- if (this.gameTime <= 0 || this.isGameOver) {
696
- clearInterval(timer);
697
- this.endGame();
698
- }
699
- }, 1000);
700
- }
701
-
702
- updateParticles() {
703
- for (let i = this.particles.length - 1; i >= 0; i--) {
704
- const particle = this.particles[i];
705
- if (!particle.update()) {
706
- particle.destroy(this.scene);
707
- this.particles.splice(i, 1);
708
  }
709
- }
710
- }
711
 
712
- createExplosion(position) {
713
- for (let i = 0; i < PARTICLE_COUNT; i++) {
714
- this.particles.push(new Particle(this.scene, position));
715
- }
 
716
  }
717
 
718
  checkCollisions() {
719
- if (this.isLoading || !this.tank.isLoaded) return;
720
 
721
- const tankPosition = this.tank.getPosition();
722
- const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
723
 
724
- // ์  ์ด์•Œ๊ณผ์˜ ์ถฉ๋Œ ๊ฒ€์‚ฌ๋ฅผ ์ตœ์ ํ™”
725
- for (const enemy of this.enemies) {
726
- if (!enemy.mesh || !enemy.isLoaded) continue;
727
 
728
- for (let i = enemy.bullets.length - 1; i >= 0; i--) {
729
- const bullet = enemy.bullets[i];
730
- const distance = bullet.position.distanceTo(tankPosition);
731
-
732
- if (distance > 50) continue; // ๋จผ ์ด์•Œ์€ ๊ฒ€์‚ฌ ์Šคํ‚ต
733
-
734
- if (distance < 1) {
735
- if (this.tank.takeDamage(10)) {
736
- this.endGame();
737
- return;
 
 
 
 
 
 
738
  }
739
- this.dispose(bullet);
740
- enemy.bullets.splice(i, 1);
741
- this.createExplosion(bullet.position);
742
- document.getElementById('health').style.width =
743
- `${(this.tank.health / MAX_HEALTH) * 100}%`;
744
  }
745
  }
 
 
 
 
 
 
 
 
 
 
746
  }
747
 
748
- // ๊ฑด๋ฌผ๊ณผ์˜ ์ถฉ๋Œ ๊ฒ€์‚ฌ ์ตœ์ ํ™”
749
- for (const building of this.buildings) {
750
- const buildingBox = new THREE.Box3().setFromObject(building);
751
- if (tankBoundingBox.intersectsBox(buildingBox)) {
752
- this.tank.body.position.copy(this.previousTankPosition);
753
- break;
 
 
 
 
 
 
 
 
754
  }
755
  }
756
-
757
- this.previousTankPosition = this.tank.body.position.clone();
758
- }
759
 
760
  endGame() {
761
  this.isGameOver = true;
@@ -783,6 +658,7 @@ createBuildings() {
783
  document.body.appendChild(gameOverDiv);
784
  }
785
 
 
786
  animate() {
787
  if (this.isGameOver) return;
788
 
@@ -843,6 +719,8 @@ createBuildings() {
843
  }
844
  }
845
 
 
 
846
  // HTML์˜ startGame ํ•จ์ˆ˜์™€ ์—ฐ๊ฒฐ
847
  window.startGame = function() {
848
  document.getElementById('startScreen').style.display = 'none';
 
330
  this.disposedObjects = new Set(); // ์ œ๊ฑฐ๋œ ๊ฐ์ฒด ์ถ”์ 
331
  }
332
 
333
+ // ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์„œ๋“œ
334
  dispose(object) {
335
  if (this.disposedObjects.has(object)) return;
336
 
 
345
  if (object.parent) object.parent.remove(object);
346
  this.disposedObjects.add(object);
347
  }
348
+
349
+ // ์ด์•Œ ์ •๋ฆฌ
350
  cleanupBullets() {
351
  // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ ์ •๋ฆฌ
352
  for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
 
357
  this.tank.bullets.splice(i, 1);
358
  }
359
  }
360
+
361
+ // ์  ์ด์•Œ ์ •๋ฆฌ
362
  this.enemies.forEach(enemy => {
363
  for (let i = enemy.bullets.length - 1; i >= 0; i--) {
364
  const bullet = enemy.bullets[i];
 
370
  }
371
  });
372
  }
 
 
373
 
374
  async initialize() {
375
+ try {
376
+ // ์กฐ๋ช… ์„ค์ •
377
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
378
+ this.scene.add(ambientLight);
379
+
380
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
381
+ directionalLight.position.set(50, 50, 50);
382
+ directionalLight.castShadow = true;
383
+ directionalLight.shadow.mapSize.width = 2048;
384
+ directionalLight.shadow.mapSize.height = 2048;
385
+ this.scene.add(directionalLight);
386
+
387
+ // ์ง€ํ˜• ์ƒ์„ฑ
388
+ const ground = new THREE.Mesh(
389
+ new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE),
390
+ new THREE.MeshStandardMaterial({
391
+ color: 0x333333,
392
+ roughness: 0.9,
393
+ metalness: 0.1
394
+ })
395
+ );
396
+ ground.rotation.x = -Math.PI / 2;
397
+ ground.receiveShadow = true;
398
+ this.scene.add(ground);
 
 
 
 
 
 
 
 
 
399
 
400
+ // ๊ฑด๋ฌผ ์ƒ์„ฑ
401
+ await this.createBuildings();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
+ // ํƒฑํฌ ์ดˆ๊ธฐํ™”
404
+ await this.tank.initialize(this.scene, this.loader);
405
+ if (!this.tank.isLoaded) {
406
+ throw new Error('Tank loading failed');
407
+ }
 
 
 
 
 
 
408
 
409
+ // ์นด๋ฉ”๋ผ ์ดˆ๊ธฐ ์„ค์ •
410
+ const tankPosition = this.tank.getPosition();
411
+ this.camera.position.set(
412
+ tankPosition.x,
413
+ tankPosition.y + 15,
414
+ tankPosition.z - 30
415
+ );
416
+ this.camera.lookAt(new THREE.Vector3(
417
+ tankPosition.x,
418
+ tankPosition.y + 2,
419
+ tankPosition.z
420
+ ));
421
 
422
+ // ๋กœ๋”ฉ ์™„๋ฃŒ
423
+ this.isLoading = false;
424
+ document.getElementById('loading').style.display = 'none';
 
 
 
 
 
425
 
426
+ // ๊ฒŒ์ž„ ์‹œ์ž‘
427
+ this.animate();
428
+ this.spawnEnemies();
429
+ this.startGameTimer();
 
 
 
 
 
 
 
430
 
431
+ } catch (error) {
432
+ console.error('Game initialization error:', error);
433
+ this.handleLoadingError();
 
 
 
434
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
+ setupEventListeners() {
438
+ document.addEventListener('keydown', (event) => {
439
+ if (this.isLoading) return;
440
+ switch(event.code) {
441
+ case 'KeyW': this.keys.forward = true; break;
442
+ case 'KeyS': this.keys.backward = true; break;
443
+ case 'KeyA': this.keys.left = true; break;
444
+ case 'KeyD': this.keys.right = true; break;
445
+ }
446
+ });
447
 
448
+ document.addEventListener('keyup', (event) => {
449
+ if (this.isLoading) return;
450
+ switch(event.code) {
451
+ case 'KeyW': this.keys.forward = false; break;
452
+ case 'KeyS': this.keys.backward = false; break;
453
+ case 'KeyA': this.keys.left = false; break;
454
+ case 'KeyD': this.keys.right = false; break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  }
456
+ });
 
457
 
458
+ document.addEventListener('mousemove', (event) => {
459
+ if (this.isLoading || !document.pointerLockElement) return;
460
+ this.mouse.x += event.movementX * 0.002;
461
+ this.mouse.y += event.movementY * 0.002;
 
 
 
462
  });
 
 
 
 
 
463
 
464
+ document.addEventListener('click', () => {
465
+ if (!document.pointerLockElement) {
466
+ document.body.requestPointerLock();
467
+ } else {
468
+ const bullet = this.tank.shoot(this.scene);
469
+ if (bullet) {
470
+ // ๋ฐœ์‚ฌ ํšจ๊ณผ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
471
+ }
472
+ }
473
+ });
 
 
 
 
474
 
475
+ document.addEventListener('pointerlockchange', () => {
476
+ if (!document.pointerLockElement) {
477
+ this.mouse.x = 0;
478
+ this.mouse.y = 0;
479
+ }
480
  });
 
481
 
482
+ window.addEventListener('resize', () => {
483
+ this.camera.aspect = window.innerWidth / window.innerHeight;
484
+ this.camera.updateProjectionMatrix();
485
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
486
+ });
 
 
 
 
 
487
  }
488
 
489
+ handleMovement() {
490
+ if (!this.tank.isLoaded) return;
491
+
492
+ const direction = new THREE.Vector3();
493
+
494
+ if (this.keys.forward) direction.z += 1;
495
+ if (this.keys.backward) direction.z -= 1;
496
+ if (this.keys.left) direction.x -= 1;
497
+ if (this.keys.right) direction.x += 1;
498
+
499
+ if (direction.length() > 0) {
500
+ direction.normalize();
501
+
502
+ if (this.keys.left) this.tank.rotate(-1);
503
+ if (this.keys.right) this.tank.rotate(1);
504
+
505
+ direction.applyEuler(this.tank.body.rotation);
506
+ this.tank.move(direction);
507
  }
508
 
509
+ const mouseVector = new THREE.Vector2(this.mouse.x, -this.mouse.y);
510
+ const rotationAngle = Math.atan2(mouseVector.x, mouseVector.y);
511
+
512
+ if (this.tank.turretGroup) {
513
+ this.tank.turretGroup.rotation.y = rotationAngle;
 
 
 
514
  }
515
+
516
+ const tankPos = this.tank.getPosition();
517
+ const cameraDistance = 30;
518
+ const cameraHeight = 15;
519
+ const lookAtHeight = 5;
520
 
521
+ const tankRotation = this.tank.body.rotation.y;
522
+
523
+ this.camera.position.set(
524
+ tankPos.x - Math.sin(tankRotation) * cameraDistance,
525
+ tankPos.y + cameraHeight,
526
+ tankPos.z - Math.cos(tankRotation) * cameraDistance
527
+ );
528
 
529
+ const lookAtPoint = new THREE.Vector3(
530
+ tankPos.x + Math.sin(tankRotation) * 10,
531
+ tankPos.y + lookAtHeight,
532
+ tankPos.z + Math.cos(tankRotation) * 10
533
+ );
534
+
535
+ this.camera.lookAt(lookAtPoint);
536
+ }
 
 
 
 
537
 
538
+ animate() {
539
+ if (this.isGameOver) return;
540
 
541
+ requestAnimationFrame(() => this.animate());
 
 
 
 
 
 
 
542
 
543
+ const currentTime = performance.now();
544
+ const deltaTime = (currentTime - this.lastTime) / 1000;
545
+ this.lastTime = currentTime;
546
 
547
+ if (this.isLoading) {
548
+ this.renderer.render(this.scene, this.camera);
549
+ return;
550
+ }
551
 
552
+ if (deltaTime < 1/60) return;
 
553
 
554
+ this.handleMovement();
555
+ this.tank.update(this.mouse.x, this.mouse.y);
556
+
557
+ const tankPosition = this.tank.getPosition();
558
+
559
+ this.enemies.forEach(enemy => {
560
+ if (!enemy.mesh || !enemy.isLoaded) return;
561
 
562
+ const distance = enemy.mesh.position.distanceTo(tankPosition);
563
+ if (distance > 200) return;
564
+
565
+ enemy.update(tankPosition);
566
+ if (distance < ENEMY_CONFIG.ATTACK_RANGE) {
567
+ enemy.shoot(tankPosition);
 
 
 
 
 
 
 
 
568
  }
569
+ });
 
570
 
571
+ this.updateParticles();
572
+ this.checkCollisions();
573
+ this.cleanupBullets();
574
+ this.updateUI();
575
+ this.renderer.render(this.scene, this.camera);
576
  }
577
 
578
  checkCollisions() {
579
+ if (this.isLoading || !this.tank.isLoaded) return;
580
 
581
+ const tankPosition = this.tank.getPosition();
582
+ const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
583
 
584
+ for (const enemy of this.enemies) {
585
+ if (!enemy.mesh || !enemy.isLoaded) continue;
 
586
 
587
+ for (let i = enemy.bullets.length - 1; i >= 0; i--) {
588
+ const bullet = enemy.bullets[i];
589
+ const distance = bullet.position.distanceTo(tankPosition);
590
+
591
+ if (distance > 50) continue;
592
+
593
+ if (distance < 1) {
594
+ if (this.tank.takeDamage(10)) {
595
+ this.endGame();
596
+ return;
597
+ }
598
+ this.dispose(bullet);
599
+ enemy.bullets.splice(i, 1);
600
+ this.createExplosion(bullet.position);
601
+ document.getElementById('health').style.width =
602
+ `${(this.tank.health / MAX_HEALTH) * 100}%`;
603
  }
 
 
 
 
 
604
  }
605
  }
606
+
607
+ for (const building of this.buildings) {
608
+ const buildingBox = new THREE.Box3().setFromObject(building);
609
+ if (tankBoundingBox.intersectsBox(buildingBox)) {
610
+ this.tank.body.position.copy(this.previousTankPosition);
611
+ break;
612
+ }
613
+ }
614
+
615
+ this.previousTankPosition = this.tank.body.position.clone();
616
  }
617
 
618
+ updateUI() {
619
+ const healthBar = document.getElementById('health');
620
+ if (healthBar) {
621
+ healthBar.style.width = `${(this.tank.health / MAX_HEALTH) * 100}%`;
622
+ }
623
+
624
+ const timeElement = document.getElementById('time');
625
+ if (timeElement) {
626
+ timeElement.textContent = `Time: ${this.gameTime}s`;
627
+ }
628
+
629
+ const scoreElement = document.getElementById('score');
630
+ if (scoreElement) {
631
+ scoreElement.textContent = `Score: ${this.score}`;
632
  }
633
  }
 
 
 
634
 
635
  endGame() {
636
  this.isGameOver = true;
 
658
  document.body.appendChild(gameOverDiv);
659
  }
660
 
661
+
662
  animate() {
663
  if (this.isGameOver) return;
664
 
 
719
  }
720
  }
721
 
722
+
723
+
724
  // HTML์˜ startGame ํ•จ์ˆ˜์™€ ์—ฐ๊ฒฐ
725
  window.startGame = function() {
726
  document.getElementById('startScreen').style.display = 'none';