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

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +268 -186
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, 1, 1);
857
  const groundMaterial = new THREE.MeshStandardMaterial({
858
  color: 0xD2B48C,
859
  roughness: 0.8,
@@ -865,10 +865,41 @@ class Game {
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,16 +907,28 @@ class Game {
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,11 +938,26 @@ class Game {
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,88 +1037,64 @@ class Game {
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,123 +1517,171 @@ class Game {
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() {
 
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, 100, 100);
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
+ // ์ง€ํ˜• ๋†’์ด ์„ค์ •
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
  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
  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
  }
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
  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() {