cutechicken commited on
Commit
ce037c6
โ€ข
1 Parent(s): c0a7f64

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +186 -268
game.js CHANGED
@@ -825,17 +825,17 @@ class Game {
825
  mainLight.castShadow = true;
826
 
827
  // ๊ทธ๋ฆผ์ž ํ’ˆ์งˆ ํ–ฅ์ƒ
828
- mainLight.shadow.mapSize.width = 4096; // ๊ทธ๋ฆผ์ž ํ•ด์ƒ๋„ ์ฆ๊ฐ€
829
  mainLight.shadow.mapSize.height = 4096;
830
  mainLight.shadow.camera.near = 0.5;
831
- mainLight.shadow.camera.far = MAP_SIZE * 2; // ๊ทธ๋ฆผ์ž ๊ฑฐ๋ฆฌ ์ฆ๊ฐ€
832
- mainLight.shadow.camera.left = -MAP_SIZE; // ๊ทธ๋ฆผ์ž ์˜์—ญ ํ™•์žฅ
833
  mainLight.shadow.camera.right = MAP_SIZE;
834
  mainLight.shadow.camera.top = MAP_SIZE;
835
  mainLight.shadow.camera.bottom = -MAP_SIZE;
836
  mainLight.shadow.bias = -0.001;
837
  mainLight.shadow.radius = 2;
838
- mainLight.shadow.normalBias = 0.02; // ๊ทธ๋ฆผ์ž ์•„ํ‹ฐํŒฉํŠธ ๊ฐ์†Œ
839
 
840
  this.scene.add(mainLight);
841
 
@@ -852,8 +852,8 @@ class Game {
852
  );
853
  this.scene.add(hemisphereLight);
854
 
855
- // ์ง€ํ˜• ์ƒ์„ฑ ์ˆ˜์ •
856
- const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 100, 100);
857
  const groundMaterial = new THREE.MeshStandardMaterial({
858
  color: 0xD2B48C,
859
  roughness: 0.8,
@@ -865,41 +865,10 @@ class Game {
865
  ground.rotation.x = -Math.PI / 2;
866
  ground.receiveShadow = true;
867
 
868
- // ์ง€ํ˜• ๋†’์ด ์„ค์ •
869
  const vertices = ground.geometry.attributes.position.array;
870
- const heightScale = 15;
871
- const baseFrequency = 0.008;
872
-
873
- // ํ‰์ง€ ์˜์—ญ ์ •์˜
874
- const flatlandRadius = MAP_SIZE * 0.3;
875
- const transitionZone = MAP_SIZE * 0.1;
876
-
877
  for (let i = 0; i < vertices.length; i += 3) {
878
- const x = vertices[i];
879
- const y = vertices[i + 1];
880
-
881
- const distanceFromCenter = Math.sqrt(x * x + y * y);
882
-
883
- if (distanceFromCenter < flatlandRadius) {
884
- vertices[i + 2] = 0;
885
- }
886
- else if (distanceFromCenter < flatlandRadius + transitionZone) {
887
- const transitionFactor = (distanceFromCenter - flatlandRadius) / transitionZone;
888
- let height = 0;
889
-
890
- height += Math.sin(x * baseFrequency) * Math.cos(y * baseFrequency) * heightScale;
891
- height += Math.sin(x * baseFrequency * 2) * Math.cos(y * baseFrequency * 2) * (heightScale * 0.5);
892
- height += Math.sin(x * baseFrequency * 4) * Math.cos(y * baseFrequency * 4) * (heightScale * 0.25);
893
-
894
- vertices[i + 2] = height * transitionFactor;
895
- }
896
- else {
897
- let height = 0;
898
- height += Math.sin(x * baseFrequency) * Math.cos(y * baseFrequency) * heightScale;
899
- height += Math.sin(x * baseFrequency * 2) * Math.cos(y * baseFrequency * 2) * (heightScale * 0.5);
900
- height += Math.sin(x * baseFrequency * 4) * Math.cos(y * baseFrequency * 4) * (heightScale * 0.25);
901
- vertices[i + 2] = height;
902
- }
903
  }
904
 
905
  ground.geometry.attributes.position.needsUpdate = true;
@@ -907,28 +876,16 @@ class Game {
907
  this.ground = ground;
908
  this.scene.add(ground);
909
 
910
- // ๋“ฑ๊ณ ์„  ํšจ๊ณผ
911
- const contourMaterial = new THREE.LineBasicMaterial({
912
- color: 0x000000,
913
- opacity: 0.15,
914
- transparent: true
915
- });
916
-
917
- const contourLines = new THREE.LineSegments(
918
- new THREE.EdgesGeometry(groundGeometry),
919
- contourMaterial
920
- );
921
- contourLines.rotation.x = -Math.PI / 2;
922
- contourLines.position.y = 0.1;
923
- this.scene.add(contourLines);
924
-
925
- // ๊ฒฉ์ž ํšจ๊ณผ
926
- const gridHelper = new THREE.GridHelper(flatlandRadius * 2, 50, 0x000000, 0x000000);
927
  gridHelper.material.opacity = 0.1;
928
  gridHelper.material.transparent = true;
929
  gridHelper.position.y = 0.1;
930
  this.scene.add(gridHelper);
931
 
 
 
 
932
  // ์‚ฌ๋ง‰ ์žฅ์‹ ์ถ”๊ฐ€
933
  await this.addDesertDecorations();
934
 
@@ -938,26 +895,11 @@ class Game {
938
  throw new Error('Tank loading failed');
939
  }
940
 
941
- // ์Šคํฐ ์œ„์น˜ ๊ฒ€์ฆ ๋ฐ ์žฌ์‹œ์ž‘ ๋กœ์ง
942
  const spawnPos = this.findValidSpawnPosition();
943
- const heightAtSpawn = this.getHeightAtPosition(spawnPos.x, spawnPos.z);
944
- const slopeCheckPoints = [
945
- { x: spawnPos.x + 2, z: spawnPos.z },
946
- { x: spawnPos.x - 2, z: spawnPos.z },
947
- { x: spawnPos.x, z: spawnPos.z + 2 },
948
- { x: spawnPos.x, z: spawnPos.z - 2 }
949
- ];
950
-
951
- const slopes = slopeCheckPoints.map(point => {
952
- const pointHeight = this.getHeightAtPosition(point.x, point.z);
953
- return Math.abs(pointHeight - heightAtSpawn) / 2;
954
- });
955
-
956
- const maxSlope = Math.max(...slopes);
957
- if (maxSlope > 0.3) {
958
- location.reload();
959
- return;
960
- }
961
 
962
  // ์นด๋ฉ”๋ผ ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ •
963
  const tankPosition = this.tank.getPosition();
@@ -1037,64 +979,88 @@ class Game {
1037
  }
1038
 
1039
  async addDesertDecorations() {
1040
- // ๋ฐ”์œ„ ์ƒ์„ฑ
1041
- const rockGeometries = [
1042
- new THREE.DodecahedronGeometry(3),
1043
- new THREE.DodecahedronGeometry(2),
1044
- new THREE.DodecahedronGeometry(4)
1045
- ];
1046
 
1047
- const rockMaterial = new THREE.MeshStandardMaterial({
1048
- color: 0x8B4513,
1049
- roughness: 0.9,
1050
- metalness: 0.1
1051
- });
 
1052
 
1053
- for (let i = 0; i < 100; i++) {
1054
- const rockGeometry = rockGeometries[Math.floor(Math.random() * rockGeometries.length)];
1055
- const rock = new THREE.Mesh(rockGeometry, rockMaterial);
1056
- rock.position.set(
1057
- (Math.random() - 0.5) * MAP_SIZE * 0.9,
1058
- Math.random() * 2,
1059
- (Math.random() - 0.5) * MAP_SIZE * 0.9
1060
- );
1061
-
1062
- rock.rotation.set(
1063
- Math.random() * Math.PI,
1064
- Math.random() * Math.PI,
1065
- Math.random() * Math.PI
1066
- );
1067
-
1068
- rock.scale.set(
1069
- 1 + Math.random() * 0.5,
1070
- 1 + Math.random() * 0.5,
1071
- 1 + Math.random() * 0.5
1072
- );
1073
-
1074
- rock.castShadow = true;
1075
- rock.receiveShadow = true;
1076
- this.scene.add(rock);
1077
- }
1078
 
1079
- // ์„ ์ธ์žฅ ์ถ”๊ฐ€ (๊ฐ„๋‹จํ•œ geometry๋กœ ํ‘œํ˜„)
1080
- const cactusGeometry = new THREE.CylinderGeometry(0.5, 0.7, 4, 8);
1081
- const cactusMaterial = new THREE.MeshStandardMaterial({
1082
- color: 0x2F4F2F,
1083
- roughness: 0.8
1084
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
 
1086
- for (let i = 0; i < 50; i++) {
1087
- const cactus = new THREE.Mesh(cactusGeometry, cactusMaterial);
1088
- cactus.position.set(
1089
- (Math.random() - 0.5) * MAP_SIZE * 0.8,
1090
- 2,
1091
- (Math.random() - 0.5) * MAP_SIZE * 0.8
1092
- );
1093
- cactus.castShadow = true;
1094
- cactus.receiveShadow = true;
1095
- this.scene.add(cactus);
1096
- }
1097
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1098
 
1099
  getHeightAtPosition(x, z) {
1100
  if (!this.ground) return 0;
@@ -1517,171 +1483,123 @@ class Game {
1517
  const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
1518
 
1519
  const tankPosition = this.tank.getPosition();
1520
- // ์  ์ด์•Œ๊ณผ ํ”Œ๋ ˆ์ด์–ด ํƒฑํฌ ์ถฉ๋Œ ์ฒดํฌ
 
 
 
 
 
 
 
 
 
 
1521
  this.enemies.forEach(enemy => {
1522
  if (!enemy.mesh || !enemy.isLoaded) return;
1523
 
1524
  enemy.bullets.forEach(bullet => {
1525
- const distance = bullet.position.distanceTo(tankPosition);
1526
- if (distance < 1) {
1527
- // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์‚ฌ์šด๋“œ ์žฌ์ƒ
1528
- const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
1529
- const beatAudio = new Audio(randomBeatSound);
1530
- beatAudio.play();
1531
-
1532
- if (this.tank.takeDamage(250)) { // ๋ฐ๋ฏธ์ง€๋ฅผ 250์œผ๋กœ ์ˆ˜์ •
1533
- this.endGame();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1534
  }
1535
- this.scene.remove(bullet);
1536
- enemy.bullets = enemy.bullets.filter(b => b !== bullet);
1537
-
1538
- this.createExplosion(bullet.position);
1539
- document.getElementById('health').style.width =
1540
- `${(this.tank.health / MAX_HEALTH) * 100}%`;
1541
  }
1542
  });
1543
  });
1544
 
1545
- // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ๊ณผ ์  ์ถฉ๋Œ ์ฒดํฌ
1546
  this.tank.bullets.forEach((bullet, bulletIndex) => {
1547
- this.enemies.forEach((enemy, enemyIndex) => {
1548
- if (!enemy.mesh || !enemy.isLoaded) return;
1549
 
1550
- const distance = bullet.position.distanceTo(enemy.mesh.position);
1551
- if (distance < 2) {
1552
- // ์  ํ”ผ๊ฒฉ ์‚ฌ์šด๋“œ ์žฌ์ƒ
1553
- const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1554
- const hitAudio = new Audio(randomHitSound);
1555
- hitAudio.play();
1556
-
1557
- if (enemy.takeDamage(50)) {
1558
- enemy.destroy();
1559
- this.enemies.splice(enemyIndex, 1);
1560
- this.score += 100;
1561
- document.getElementById('score').textContent = `Score: ${this.score}`;
1562
- }
1563
  this.scene.remove(bullet);
1564
  this.tank.bullets.splice(bulletIndex, 1);
1565
- this.createExplosion(bullet.position);
 
1566
  }
1567
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1568
  });
1569
 
1570
- // ํ”Œ๋ ˆ์ด์–ด ํƒฑํฌ์™€ ์  ์ „์ฐจ ์ถฉ๋Œ ์ฒดํฌ
1571
- const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
1572
  this.enemies.forEach(enemy => {
1573
  if (!enemy.mesh || !enemy.isLoaded) return;
1574
 
1575
- const enemyBoundingBox = new THREE.Box3().setFromObject(enemy.mesh);
1576
- if (tankBoundingBox.intersectsBox(enemyBoundingBox)) {
 
 
 
 
 
 
 
 
 
 
1577
  this.tank.body.position.copy(this.previousTankPosition);
1578
  }
 
 
 
1579
  });
1580
 
1581
  // ์ด์ „ ์œ„์น˜ ์ €์žฅ
1582
  this.previousTankPosition.copy(this.tank.body.position);
1583
  }
1584
- endGame() {
1585
- if (this.isGameOver) return;
1586
-
1587
- this.isGameOver = true;
1588
-
1589
- // ์‚ฌ๋ง ์‚ฌ์šด๋“œ ์žฌ์ƒ
1590
- const deathSounds = ['sounds/death1.ogg', 'sounds/death2.ogg'];
1591
- const randomDeathSound = deathSounds[Math.floor(Math.random() * deathSounds.length)];
1592
- const deathAudio = new Audio(randomDeathSound);
1593
- deathAudio.play();
1594
-
1595
- if (this.gameTimer) {
1596
- clearInterval(this.gameTimer);
1597
- }
1598
-
1599
- if (this.animationFrameId) {
1600
- cancelAnimationFrame(this.animationFrameId);
1601
- }
1602
-
1603
- document.exitPointerLock();
1604
-
1605
- const gameOverDiv = document.createElement('div');
1606
- gameOverDiv.style.position = 'absolute';
1607
- gameOverDiv.style.top = '50%';
1608
- gameOverDiv.style.left = '50%';
1609
- gameOverDiv.style.transform = 'translate(-50%, -50%)';
1610
- gameOverDiv.style.color = '#0f0';
1611
- gameOverDiv.style.fontSize = '48px';
1612
- gameOverDiv.style.backgroundColor = 'rgba(0, 20, 0, 0.7)';
1613
- gameOverDiv.style.padding = '20px';
1614
- gameOverDiv.style.borderRadius = '10px';
1615
- gameOverDiv.style.textAlign = 'center';
1616
- gameOverDiv.innerHTML = `
1617
- Game Over<br>
1618
- Score: ${this.score}<br>
1619
- Time Survived: ${GAME_DURATION - this.gameTime}s<br>
1620
- <button onclick="location.reload()"
1621
- style="font-size: 24px; padding: 10px; margin-top: 20px;
1622
- cursor: pointer; background: #0f0; border: none;
1623
- color: black; border-radius: 5px;">
1624
- Play Again
1625
- </button>
1626
- `;
1627
- document.body.appendChild(gameOverDiv);
1628
- }
1629
-
1630
- updateUI() {
1631
- if (!this.isGameOver) {
1632
- const healthBar = document.getElementById('health');
1633
- if (healthBar) {
1634
- healthBar.style.width = `${(this.tank.health / MAX_HEALTH) * 100}%`;
1635
- }
1636
-
1637
- const timeElement = document.getElementById('time');
1638
- if (timeElement) {
1639
- timeElement.textContent = `Time: ${this.gameTime}s`;
1640
- }
1641
-
1642
- const scoreElement = document.getElementById('score');
1643
- if (scoreElement) {
1644
- scoreElement.textContent = `Score: ${this.score}`;
1645
- }
1646
- }
1647
- }
1648
-
1649
- animate() {
1650
- if (this.isGameOver) {
1651
- if (this.animationFrameId) {
1652
- cancelAnimationFrame(this.animationFrameId);
1653
- }
1654
- return;
1655
- }
1656
-
1657
- this.animationFrameId = requestAnimationFrame(() => this.animate());
1658
-
1659
- const currentTime = performance.now();
1660
- const deltaTime = (currentTime - this.lastTime) / 1000;
1661
- this.lastTime = currentTime;
1662
-
1663
- if (!this.isLoading) {
1664
- this.handleMovement();
1665
- this.tank.update(this.mouse.x, this.mouse.y, this.scene);
1666
-
1667
- const tankPosition = this.tank.getPosition();
1668
- this.enemies.forEach(enemy => {
1669
- enemy.update(tankPosition);
1670
-
1671
- if (enemy.isLoaded && enemy.mesh.position.distanceTo(tankPosition) < ENEMY_CONFIG.ATTACK_RANGE) {
1672
- enemy.shoot(tankPosition);
1673
- }
1674
- });
1675
-
1676
- this.updateParticles();
1677
- this.checkCollisions();
1678
- this.updateUI();
1679
- this.updateRadar(); // ๋ ˆ์ด๋” ์—…๋ฐ์ดํŠธ ์ถ”๊ฐ€
1680
- }
1681
-
1682
- this.renderer.render(this.scene, this.camera);
1683
- }
1684
- }
1685
 
1686
  // Start game
1687
  window.startGame = function() {
 
825
  mainLight.castShadow = true;
826
 
827
  // ๊ทธ๋ฆผ์ž ํ’ˆ์งˆ ํ–ฅ์ƒ
828
+ mainLight.shadow.mapSize.width = 4096;
829
  mainLight.shadow.mapSize.height = 4096;
830
  mainLight.shadow.camera.near = 0.5;
831
+ mainLight.shadow.camera.far = MAP_SIZE * 2;
832
+ mainLight.shadow.camera.left = -MAP_SIZE;
833
  mainLight.shadow.camera.right = MAP_SIZE;
834
  mainLight.shadow.camera.top = MAP_SIZE;
835
  mainLight.shadow.camera.bottom = -MAP_SIZE;
836
  mainLight.shadow.bias = -0.001;
837
  mainLight.shadow.radius = 2;
838
+ mainLight.shadow.normalBias = 0.02;
839
 
840
  this.scene.add(mainLight);
841
 
 
852
  );
853
  this.scene.add(hemisphereLight);
854
 
855
+ // ์ง€ํ˜• ์ƒ์„ฑ ์ˆ˜์ • - ์™„์ „ํžˆ ํ‰ํ‰ํ•˜๊ฒŒ
856
+ const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 1, 1);
857
  const groundMaterial = new THREE.MeshStandardMaterial({
858
  color: 0xD2B48C,
859
  roughness: 0.8,
 
865
  ground.rotation.x = -Math.PI / 2;
866
  ground.receiveShadow = true;
867
 
868
+ // ๋ชจ๋“  ์ •์ ์˜ ๋†’์ด๋ฅผ 0์œผ๋กœ ์„ค์ •
869
  const vertices = ground.geometry.attributes.position.array;
 
 
 
 
 
 
 
870
  for (let i = 0; i < vertices.length; i += 3) {
871
+ vertices[i + 2] = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872
  }
873
 
874
  ground.geometry.attributes.position.needsUpdate = true;
 
876
  this.ground = ground;
877
  this.scene.add(ground);
878
 
879
+ // ๊ฒฉ์ž ํšจ๊ณผ ์ถ”๊ฐ€
880
+ const gridHelper = new THREE.GridHelper(MAP_SIZE, 50, 0x000000, 0x000000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  gridHelper.material.opacity = 0.1;
882
  gridHelper.material.transparent = true;
883
  gridHelper.position.y = 0.1;
884
  this.scene.add(gridHelper);
885
 
886
+ // ์žฅ์• ๋ฌผ ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
887
+ this.obstacles = [];
888
+
889
  // ์‚ฌ๋ง‰ ์žฅ์‹ ์ถ”๊ฐ€
890
  await this.addDesertDecorations();
891
 
 
895
  throw new Error('Tank loading failed');
896
  }
897
 
898
+ // ์Šคํฐ ์œ„์น˜ ๊ฒ€์ฆ
899
  const spawnPos = this.findValidSpawnPosition();
900
+
901
+ // ํƒฑํฌ ์Šคํฐ ์œ„์น˜ ์„ค์ •
902
+ this.tank.body.position.copy(spawnPos);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
903
 
904
  // ์นด๋ฉ”๋ผ ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ •
905
  const tankPosition = this.tank.getPosition();
 
979
  }
980
 
981
  async addDesertDecorations() {
982
+ this.obstacles = []; // ์žฅ์• ๋ฌผ ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
 
 
 
 
 
983
 
984
+ // ๋ฐ”์œ„ ์ƒ์„ฑ (200๊ฐœ๋กœ ์ฆ๊ฐ€)
985
+ const rockGeometries = [
986
+ new THREE.DodecahedronGeometry(3),
987
+ new THREE.DodecahedronGeometry(2),
988
+ new THREE.DodecahedronGeometry(4)
989
+ ];
990
 
991
+ const rockMaterial = new THREE.MeshStandardMaterial({
992
+ color: 0x8B4513,
993
+ roughness: 0.9,
994
+ metalness: 0.1
995
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
996
 
997
+ for (let i = 0; i < 200; i++) {
998
+ const rockGeometry = rockGeometries[Math.floor(Math.random() * rockGeometries.length)];
999
+ const rock = new THREE.Mesh(rockGeometry, rockMaterial);
1000
+
1001
+ const position = new THREE.Vector3(
1002
+ (Math.random() - 0.5) * MAP_SIZE * 0.9,
1003
+ Math.random() * 2,
1004
+ (Math.random() - 0.5) * MAP_SIZE * 0.9
1005
+ );
1006
+
1007
+ rock.position.copy(position);
1008
+ rock.rotation.set(
1009
+ Math.random() * Math.PI,
1010
+ Math.random() * Math.PI,
1011
+ Math.random() * Math.PI
1012
+ );
1013
+
1014
+ rock.scale.set(
1015
+ 1 + Math.random() * 0.5,
1016
+ 1 + Math.random() * 0.5,
1017
+ 1 + Math.random() * 0.5
1018
+ );
1019
+
1020
+ rock.castShadow = true;
1021
+ rock.receiveShadow = true;
1022
+
1023
+ // ์ถฉ๋Œ ๋ฐ•์Šค ์ƒ์„ฑ ๋ฐ ์ €์žฅ
1024
+ const boundingBox = new THREE.Box3().setFromObject(rock);
1025
+ this.obstacles.push({
1026
+ mesh: rock,
1027
+ boundingBox: boundingBox,
1028
+ type: 'rock'
1029
+ });
1030
+
1031
+ this.scene.add(rock);
1032
+ }
1033
 
1034
+ // ์„ ์ธ์žฅ ์ถ”๊ฐ€ (100๊ฐœ๋กœ ์ฆ๊ฐ€)
1035
+ const cactusGeometry = new THREE.CylinderGeometry(0.5, 0.7, 4, 8);
1036
+ const cactusMaterial = new THREE.MeshStandardMaterial({
1037
+ color: 0x2F4F2F,
1038
+ roughness: 0.8
1039
+ });
1040
+
1041
+ for (let i = 0; i < 100; i++) {
1042
+ const cactus = new THREE.Mesh(cactusGeometry, cactusMaterial);
1043
+ const position = new THREE.Vector3(
1044
+ (Math.random() - 0.5) * MAP_SIZE * 0.8,
1045
+ 2,
1046
+ (Math.random() - 0.5) * MAP_SIZE * 0.8
1047
+ );
1048
+
1049
+ cactus.position.copy(position);
1050
+ cactus.castShadow = true;
1051
+ cactus.receiveShadow = true;
1052
+
1053
+ // ์„ ์ธ์žฅ ์ถฉ๋Œ ๋ฐ•์Šค ์ƒ์„ฑ ๋ฐ ์ €์žฅ
1054
+ const boundingBox = new THREE.Box3().setFromObject(cactus);
1055
+ this.obstacles.push({
1056
+ mesh: cactus,
1057
+ boundingBox: boundingBox,
1058
+ type: 'cactus'
1059
+ });
1060
+
1061
+ this.scene.add(cactus);
1062
+ }
1063
+ }
1064
 
1065
  getHeightAtPosition(x, z) {
1066
  if (!this.ground) return 0;
 
1483
  const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
1484
 
1485
  const tankPosition = this.tank.getPosition();
1486
+ const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
1487
+
1488
+ // ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ(๋ฐ”์œ„, ์„ ์ธ์žฅ) ์ถฉ๋Œ ์ฒดํฌ
1489
+ this.obstacles.forEach(obstacle => {
1490
+ obstacle.boundingBox.setFromObject(obstacle.mesh);
1491
+ if (tankBoundingBox.intersectsBox(obstacle.boundingBox)) {
1492
+ this.tank.body.position.copy(this.previousTankPosition);
1493
+ }
1494
+ });
1495
+
1496
+ // ์  ์ด์•Œ ์ถฉ๋Œ ์ฒดํฌ
1497
  this.enemies.forEach(enemy => {
1498
  if (!enemy.mesh || !enemy.isLoaded) return;
1499
 
1500
  enemy.bullets.forEach(bullet => {
1501
+ const bulletBox = new THREE.Box3().setFromObject(bullet);
1502
+ let bulletDestroyed = false;
1503
+
1504
+ // ์ด์•Œ๊ณผ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1505
+ for (const obstacle of this.obstacles) {
1506
+ if (bulletBox.intersectsBox(obstacle.boundingBox)) {
1507
+ this.createExplosion(bullet.position);
1508
+ this.scene.remove(bullet);
1509
+ enemy.bullets = enemy.bullets.filter(b => b !== bullet);
1510
+ bulletDestroyed = true;
1511
+ break;
1512
+ }
1513
+ }
1514
+
1515
+ // ์ด์•Œ์ด ์žฅ์• ๋ฌผ์— ๋งž์ง€ ์•Š์•˜๋‹ค๋ฉด ํ”Œ๋ ˆ์ด์–ด์™€ ์ถฉ๋Œ ์ฒดํฌ
1516
+ if (!bulletDestroyed) {
1517
+ const distance = bullet.position.distanceTo(tankPosition);
1518
+ if (distance < 1) {
1519
+ const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
1520
+ const beatAudio = new Audio(randomBeatSound);
1521
+ beatAudio.play();
1522
+
1523
+ if (this.tank.takeDamage(250)) {
1524
+ this.endGame();
1525
+ }
1526
+ this.scene.remove(bullet);
1527
+ enemy.bullets = enemy.bullets.filter(b => b !== bullet);
1528
+ this.createExplosion(bullet.position);
1529
+ document.getElementById('health').style.width =
1530
+ `${(this.tank.health / MAX_HEALTH) * 100}%`;
1531
  }
 
 
 
 
 
 
1532
  }
1533
  });
1534
  });
1535
 
1536
+ // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ ์ถฉ๋Œ ์ฒดํฌ
1537
  this.tank.bullets.forEach((bullet, bulletIndex) => {
1538
+ const bulletBox = new THREE.Box3().setFromObject(bullet);
1539
+ let bulletDestroyed = false;
1540
 
1541
+ // ์ด์•Œ๊ณผ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1542
+ for (const obstacle of this.obstacles) {
1543
+ if (bulletBox.intersectsBox(obstacle.boundingBox)) {
1544
+ this.createExplosion(bullet.position);
 
 
 
 
 
 
 
 
 
1545
  this.scene.remove(bullet);
1546
  this.tank.bullets.splice(bulletIndex, 1);
1547
+ bulletDestroyed = true;
1548
+ break;
1549
  }
1550
+ }
1551
+
1552
+ // ์ด์•Œ์ด ์žฅ์• ๋ฌผ์— ๋งž์ง€ ์•Š์•˜๋‹ค๋ฉด ์ ๊ณผ ์ถฉ๋Œ ์ฒดํฌ
1553
+ if (!bulletDestroyed) {
1554
+ this.enemies.forEach((enemy, enemyIndex) => {
1555
+ if (!enemy.mesh || !enemy.isLoaded) return;
1556
+
1557
+ const distance = bullet.position.distanceTo(enemy.mesh.position);
1558
+ if (distance < 2) {
1559
+ const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1560
+ const hitAudio = new Audio(randomHitSound);
1561
+ hitAudio.play();
1562
+
1563
+ if (enemy.takeDamage(50)) {
1564
+ enemy.destroy();
1565
+ this.enemies.splice(enemyIndex, 1);
1566
+ this.score += 100;
1567
+ document.getElementById('score').textContent = `Score: ${this.score}`;
1568
+ }
1569
+ this.scene.remove(bullet);
1570
+ this.tank.bullets.splice(bulletIndex, 1);
1571
+ this.createExplosion(bullet.position);
1572
+ }
1573
+ });
1574
+ }
1575
  });
1576
 
1577
+ // ์  ํƒฑํฌ์˜ ์ถฉ๋Œ ์ฒดํฌ
 
1578
  this.enemies.forEach(enemy => {
1579
  if (!enemy.mesh || !enemy.isLoaded) return;
1580
 
1581
+ const enemyBox = new THREE.Box3().setFromObject(enemy.mesh);
1582
+
1583
+ // ์  ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1584
+ for (const obstacle of this.obstacles) {
1585
+ if (enemyBox.intersectsBox(obstacle.boundingBox)) {
1586
+ enemy.mesh.position.copy(enemy.lastPosition || enemy.mesh.position);
1587
+ break;
1588
+ }
1589
+ }
1590
+
1591
+ // ์  ํƒฑํฌ์™€ ํ”Œ๋ ˆ์ด์–ด ํƒฑํฌ ์ถฉ๋Œ ์ฒดํฌ
1592
+ if (tankBoundingBox.intersectsBox(enemyBox)) {
1593
  this.tank.body.position.copy(this.previousTankPosition);
1594
  }
1595
+
1596
+ // ์  ํƒฑํฌ์˜ ํ˜„์žฌ ์œ„์น˜ ์ €์žฅ
1597
+ enemy.lastPosition = enemy.mesh.position.clone();
1598
  });
1599
 
1600
  // ์ด์ „ ์œ„์น˜ ์ €์žฅ
1601
  this.previousTankPosition.copy(this.tank.body.position);
1602
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1603
 
1604
  // Start game
1605
  window.startGame = function() {