cutechicken commited on
Commit
b3db824
โ€ข
1 Parent(s): d71ef0b

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +418 -210
game.js CHANGED
@@ -43,21 +43,6 @@ class TankPlayer {
43
  this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
44
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
45
  }
46
- createCollisionBox() {
47
- // ํƒฑํฌ ๋ชธ์ฒด์— ๋งž๋Š” ์ถฉ๋Œ ๋ฐ•์Šค ์ƒ์„ฑ
48
- const dimensions = new THREE.Vector3(4, 2, 7); // ํƒฑํฌ์˜ ๋Œ€๋žต์ ์ธ ํฌ๊ธฐ
49
- const geometry = new THREE.BoxGeometry(dimensions.x, dimensions.y, dimensions.z);
50
- const material = new THREE.MeshBasicMaterial({
51
- color: 0xff0000,
52
- wireframe: true,
53
- visible: false // ๋””๋ฒ„๊น…์‹œ true๋กœ ๋ณ€๊ฒฝ
54
- });
55
-
56
- this.collisionBox = new THREE.Mesh(geometry, material);
57
- this.body.add(this.collisionBox);
58
- this.collisionBox.position.set(0, dimensions.y / 2, 0);
59
- }
60
-
61
  // ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋กœ ๋ถ„๋ฆฌ
62
  createExplosionEffect(scene, position) {
63
  // ํญ๋ฐœ ์ค‘์‹ฌ ํ”Œ๋ž˜์‹œ
@@ -185,7 +170,7 @@ class TankPlayer {
185
  this.turretGroup.add(this.turret);
186
  this.body.add(this.turretGroup);
187
 
188
- // ๊ทธ๋ฆผ์ž ์„ค์ •
189
  this.body.traverse((child) => {
190
  if (child.isMesh) {
191
  child.castShadow = true;
@@ -215,15 +200,57 @@ class TankPlayer {
215
  this.shadowPlane.position.y = 0.1;
216
  this.body.add(this.shadowPlane);
217
 
218
- // ์Šคํฐ ์œ„์น˜ ์„ค์ •
219
  const spawnPosition = new THREE.Vector3(
220
- (Math.random() - 0.5) * (MAP_SIZE * 0.8),
221
- TANK_HEIGHT,
222
  (Math.random() - 0.5) * (MAP_SIZE * 0.8)
223
  );
224
 
225
  this.body.position.copy(spawnPosition);
226
- this.createCollisionBox();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  scene.add(this.body);
228
  this.isLoaded = true;
229
  this.updateAmmoDisplay();
@@ -553,14 +580,80 @@ class Enemy {
553
  this.isLoaded = false;
554
  this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
555
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
 
557
  async initialize(loader) {
558
  try {
559
- const result = await loader.loadAsync('/models/t90.glb');
 
560
  this.mesh = result.scene;
561
  this.mesh.position.copy(this.position);
562
  this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
563
- this.setupMesh();
 
 
 
 
 
 
 
 
564
  this.isLoaded = true;
565
  } catch (error) {
566
  console.error('Error loading enemy model:', error);
@@ -568,82 +661,230 @@ class Enemy {
568
  }
569
  }
570
 
571
- setupMesh() {
572
- this.mesh.traverse(child => {
573
- if (child.isMesh) {
574
- child.castShadow = true;
575
- child.receiveShadow = true;
576
- }
577
- });
578
- this.createCollisionBox();
579
- this.scene.add(this.mesh);
580
- }
581
-
582
- createCollisionBox() {
583
- const geometry = new THREE.BoxGeometry(4, 2, 7);
584
- const material = new THREE.MeshBasicMaterial({
585
- color: 0xff0000,
586
- wireframe: true,
587
- visible: false
588
- });
589
- this.collisionBox = new THREE.Mesh(geometry, material);
590
- this.mesh.add(this.collisionBox);
591
- this.collisionBox.position.set(0, 1, 0);
592
- }
593
-
594
  update(playerPosition) {
595
- if (!this.isLoaded) return;
596
- this.moveTowardsPlayer(playerPosition);
597
- this.updateBullets();
598
- }
599
 
600
- moveTowardsPlayer(playerPosition) {
601
- const direction = new THREE.Vector3()
602
- .subVectors(playerPosition, this.mesh.position)
603
- .normalize();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604
 
605
- const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
606
- if (distanceToPlayer > 50) {
607
- this.tryMove(direction);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
  }
609
- this.mesh.lookAt(playerPosition);
610
  }
 
 
611
 
612
  shoot(playerPosition) {
613
- if (Date.now() - this.lastAttackTime < ENEMY_CONFIG.ATTACK_INTERVAL) return;
 
 
 
614
 
615
- const bullet = this.createBullet(playerPosition);
616
- this.scene.add(bullet);
617
- this.bullets.push(bullet);
618
- this.lastAttackTime = Date.now();
619
 
620
- new Audio('sounds/mbtfire5.ogg').play();
621
- }
622
 
623
- createBullet(targetPosition) {
624
- const bullet = new THREE.Mesh(
625
- new THREE.CylinderGeometry(0.2, 0.2, 2, 8),
626
- new THREE.MeshBasicMaterial({ color: 0xff0000 })
627
- );
628
 
629
- const muzzlePosition = this.getMuzzlePosition();
630
- bullet.position.copy(muzzlePosition);
631
-
632
- const direction = new THREE.Vector3()
633
- .subVectors(targetPosition, muzzlePosition)
634
- .normalize();
635
-
636
- bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
637
- return bullet;
638
- }
639
 
640
- getMuzzlePosition() {
641
- const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
642
- const position = new THREE.Vector3();
643
- this.mesh.getWorldPosition(position);
644
- muzzleOffset.applyQuaternion(this.mesh.quaternion);
645
- return position.add(muzzleOffset);
646
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
 
648
  takeDamage(damage) {
649
  this.health -= damage;
@@ -1477,71 +1718,37 @@ class Game {
1477
  checkCollisions() {
1478
  if (this.isLoading || !this.tank.isLoaded) return;
1479
 
 
1480
  const hitSounds = [
1481
  'sounds/hit1.ogg', 'sounds/hit2.ogg', 'sounds/hit3.ogg',
1482
  'sounds/hit4.ogg', 'sounds/hit5.ogg', 'sounds/hit6.ogg', 'sounds/hit7.ogg'
1483
  ];
1484
 
 
1485
  const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
1486
 
1487
  const tankPosition = this.tank.getPosition();
1488
-
1489
- // ์ „์ฐจ ์ถฉ๋Œ ๋ฐ•์Šค ํฌ๊ธฐ ์กฐ์ •
1490
- const tankBox = new THREE.Box3().setFromObject(this.tank.body);
1491
- const tankSize = new THREE.Vector3();
1492
- tankBox.getSize(tankSize);
1493
-
1494
- // ์ „์ฐจ ์ถฉ๋Œ ๋ฐ•์Šค ํฌ๊ธฐ ์ˆ˜๋™ ์กฐ์ •
1495
- const customTankBox = new THREE.Box3(
1496
- new THREE.Vector3(
1497
- tankPosition.x - tankSize.x * 0.4, // ๋„ˆ๋น„ 20% ๊ฐ์†Œ
1498
- tankPosition.y - tankSize.y * 0.3, // ๋†’์ด 40% ๊ฐ์†Œ
1499
- tankPosition.z - tankSize.z * 0.4 // ๊ธธ์ด 20% ๊ฐ์†Œ
1500
- ),
1501
- new THREE.Vector3(
1502
- tankPosition.x + tankSize.x * 0.4,
1503
- tankPosition.y + tankSize.y * 0.3,
1504
- tankPosition.z + tankSize.z * 0.4
1505
- )
1506
- );
1507
-
1508
- // ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1509
  this.obstacles.forEach(obstacle => {
1510
- if (obstacle.userData.isCollidable) {
1511
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
1512
- if (customTankBox.intersectsBox(obstacleBox)) {
1513
- this.tank.body.position.copy(this.previousTankPosition);
1514
- }
1515
  }
1516
- });
1517
-
1518
- // ์  ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1519
  this.enemies.forEach(enemy => {
1520
  if (!enemy.mesh || !enemy.isLoaded) return;
1521
 
1522
- const enemyBox = new THREE.Box3().setFromObject(enemy.mesh);
1523
- const enemySize = new THREE.Vector3();
1524
- enemyBox.getSize(enemySize);
1525
-
1526
- // ์  ์ „์ฐจ ์ถฉ๋Œ ๋ฐ•์Šค ํฌ๊ธฐ ์ˆ˜๋™ ์กฐ์ •
1527
- const customEnemyBox = new THREE.Box3(
1528
- new THREE.Vector3(
1529
- enemy.mesh.position.x - enemySize.x * 0.4,
1530
- enemy.mesh.position.y - enemySize.y * 0.3,
1531
- enemy.mesh.position.z - enemySize.z * 0.4
1532
- ),
1533
- new THREE.Vector3(
1534
- enemy.mesh.position.x + enemySize.x * 0.4,
1535
- enemy.mesh.position.y + enemySize.y * 0.3,
1536
- enemy.mesh.position.z + enemySize.z * 0.4
1537
- )
1538
- );
1539
-
1540
  const enemyPreviousPosition = enemy.mesh.position.clone();
1541
 
1542
  this.obstacles.forEach(obstacle => {
1543
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
1544
- if (customEnemyBox.intersectsBox(obstacleBox)) {
1545
  enemy.mesh.position.copy(enemyPreviousPosition);
1546
  }
1547
  });
@@ -1552,8 +1759,9 @@ class Game {
1552
  if (!enemy.mesh || !enemy.isLoaded) return;
1553
 
1554
  enemy.bullets.forEach((bullet, bulletIndex) => {
1555
- const bulletBox = new THREE.Box3().setFromObject(bullet);
1556
- if (bulletBox.intersectsBox(customTankBox)) {
 
1557
  const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
1558
  const beatAudio = new Audio(randomBeatSound);
1559
  beatAudio.play();
@@ -1562,102 +1770,102 @@ class Game {
1562
  this.endGame();
1563
  }
1564
  this.scene.remove(bullet);
1565
- enemy.bullets.splice(bulletIndex, 1);
1566
 
1567
- this.tank.createExplosionEffect(this.scene, bullet.position);
1568
  document.getElementById('health').style.width =
1569
  `${(this.tank.health / MAX_HEALTH) * 100}%`;
1570
  }
1571
  });
1572
  });
1573
 
1574
- // ํ”Œ๋ ˆ์ด์–ด ํฌํƒ„๊ณผ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1575
- for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
1576
- const bullet = this.tank.bullets[i];
1577
- const bulletBox = new THREE.Box3().setFromObject(bullet);
 
1578
 
1579
- for (const obstacle of this.obstacles) {
1580
- if (obstacle.userData.isCollidable) {
1581
- const obstacleBox = new THREE.Box3().setFromObject(obstacle);
1582
- if (bulletBox.intersectsBox(obstacleBox)) {
1583
- this.tank.createExplosionEffect(this.scene, bullet.position);
1584
- this.scene.remove(bullet);
1585
- this.tank.bullets.splice(i, 1);
1586
- break;
1587
- }
 
 
1588
  }
1589
  }
1590
  }
 
1591
 
1592
- // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ๊ณผ ์  ์ถฉ๋Œ ์ฒดํฌ
1593
- for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
1594
- const bullet = this.tank.bullets[i];
1595
- const bulletBox = new THREE.Box3().setFromObject(bullet);
1596
-
1597
- for (let j = this.enemies.length - 1; j >= 0; j--) {
1598
- const enemy = this.enemies[j];
1599
- if (!enemy.mesh || !enemy.isLoaded) continue;
1600
-
1601
- const enemyBox = new THREE.Box3().setFromObject(enemy.mesh);
1602
- const enemySize = new THREE.Vector3();
1603
- enemyBox.getSize(enemySize);
 
 
 
1604
 
1605
- // ์  ์ „์ฐจ ์ถฉ๋Œ ๋ฐ•์Šค ์กฐ์ •
1606
- const customEnemyBox = new THREE.Box3(
1607
- new THREE.Vector3(
1608
- enemy.mesh.position.x - enemySize.x * 0.4,
1609
- enemy.mesh.position.y - enemySize.y * 0.3,
1610
- enemy.mesh.position.z - enemySize.z * 0.4
1611
- ),
1612
- new THREE.Vector3(
1613
- enemy.mesh.position.x + enemySize.x * 0.4,
1614
- enemy.mesh.position.y + enemySize.y * 0.3,
1615
- enemy.mesh.position.z + enemySize.z * 0.4
1616
- )
1617
- );
1618
 
1619
- if (bulletBox.intersectsBox(customEnemyBox)) {
1620
- const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1621
- const hitAudio = new Audio(randomHitSound);
1622
- hitAudio.play();
1623
 
1624
- if (enemy.takeDamage(50)) {
1625
- enemy.destroy();
1626
- this.enemies.splice(j, 1);
1627
- this.score += 100;
1628
- document.getElementById('score').textContent = `Score: ${this.score}`;
1629
- }
1630
-
1631
- this.tank.createExplosionEffect(this.scene, bullet.position);
1632
- this.scene.remove(bullet);
1633
- this.tank.bullets.splice(i, 1);
1634
- break;
 
 
 
 
 
 
 
1635
  }
 
 
 
 
 
 
 
1636
  }
1637
  }
 
1638
 
1639
  // ํ”Œ๋ ˆ์ด์–ด ํƒฑํฌ์™€ ์  ์ „์ฐจ ์ถฉ๋Œ ์ฒดํฌ
1640
  this.enemies.forEach(enemy => {
1641
  if (!enemy.mesh || !enemy.isLoaded) return;
1642
 
1643
- const enemyBox = new THREE.Box3().setFromObject(enemy.mesh);
1644
- const enemySize = new THREE.Vector3();
1645
- enemyBox.getSize(enemySize);
1646
-
1647
- const customEnemyBox = new THREE.Box3(
1648
- new THREE.Vector3(
1649
- enemy.mesh.position.x - enemySize.x * 0.4,
1650
- enemy.mesh.position.y - enemySize.y * 0.3,
1651
- enemy.mesh.position.z - enemySize.z * 0.4
1652
- ),
1653
- new THREE.Vector3(
1654
- enemy.mesh.position.x + enemySize.x * 0.4,
1655
- enemy.mesh.position.y + enemySize.y * 0.3,
1656
- enemy.mesh.position.z + enemySize.z * 0.4
1657
- )
1658
- );
1659
-
1660
- if (customTankBox.intersectsBox(customEnemyBox)) {
1661
  this.tank.body.position.copy(this.previousTankPosition);
1662
  }
1663
  });
 
43
  this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
44
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  // ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋กœ ๋ถ„๋ฆฌ
47
  createExplosionEffect(scene, position) {
48
  // ํญ๋ฐœ ์ค‘์‹ฌ ํ”Œ๋ž˜์‹œ
 
170
  this.turretGroup.add(this.turret);
171
  this.body.add(this.turretGroup);
172
 
173
+ // ๊ทธ๋ฆผ์ž ์„ค์ •์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€
174
  this.body.traverse((child) => {
175
  if (child.isMesh) {
176
  child.castShadow = true;
 
200
  this.shadowPlane.position.y = 0.1;
201
  this.body.add(this.shadowPlane);
202
 
203
+ // ๊ฐ„๋‹จํ•œ ์Šคํฐ ์œ„์น˜ ์„ค์ •
204
  const spawnPosition = new THREE.Vector3(
205
+ (Math.random() - 0.5) * (MAP_SIZE * 0.8), // MAP_SIZE์˜ 80%๋งŒ ์‚ฌ์šฉ
206
+ TANK_HEIGHT, // ๊ณ ์ •๋œ ๋†’์ด ์‚ฌ์šฉ
207
  (Math.random() - 0.5) * (MAP_SIZE * 0.8)
208
  );
209
 
210
  this.body.position.copy(spawnPosition);
211
+
212
+ // ํญ๋ฐœ ์ดํŽ™ํŠธ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
213
+ this.createExplosionEffect = (scene, position) => {
214
+ // ํญ๋ฐœ ํŒŒํ‹ฐํด
215
+ for (let i = 0; i < 15; i++) {
216
+ const size = Math.random() * 0.2 + 0.1;
217
+ const geometry = new THREE.SphereGeometry(size);
218
+ const material = new THREE.MeshBasicMaterial({
219
+ color: Math.random() < 0.5 ? 0xff4500 : 0xff8c00
220
+ });
221
+ const particle = new THREE.Mesh(geometry, material);
222
+ particle.position.copy(position);
223
+
224
+ const speed = Math.random() * 0.3 + 0.2;
225
+ const angle = Math.random() * Math.PI * 2;
226
+ const elevation = Math.random() * Math.PI - Math.PI / 2;
227
+
228
+ particle.velocity = new THREE.Vector3(
229
+ Math.cos(angle) * Math.cos(elevation) * speed,
230
+ Math.sin(elevation) * speed,
231
+ Math.sin(angle) * Math.cos(elevation) * speed
232
+ );
233
+
234
+ particle.gravity = -0.01;
235
+ particle.life = Math.random() * 20 + 20;
236
+ particle.fadeRate = 1 / particle.life;
237
+
238
+ scene.add(particle);
239
+ window.gameInstance.particles.push({
240
+ mesh: particle,
241
+ velocity: particle.velocity,
242
+ gravity: particle.gravity,
243
+ life: particle.life,
244
+ fadeRate: particle.fadeRate
245
+ });
246
+ }
247
+
248
+ // ์ถฉ๋Œ ์‚ฌ์šด๋“œ ์žฌ์ƒ
249
+ const explosionSound = new Audio('sounds/explosion.ogg');
250
+ explosionSound.volume = 0.3;
251
+ explosionSound.play();
252
+ };
253
+
254
  scene.add(this.body);
255
  this.isLoaded = true;
256
  this.updateAmmoDisplay();
 
580
  this.isLoaded = false;
581
  this.moveSpeed = type === 'tank' ? ENEMY_MOVE_SPEED : ENEMY_MOVE_SPEED * 0.7;
582
  }
583
+ createMuzzleFlash() {
584
+ if (!this.mesh) return;
585
+
586
+ const flashGroup = new THREE.Group();
587
+
588
+ // ํ™”์—ผ ํฌ๊ธฐ ์ฆ๊ฐ€ ๋ฐ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
589
+ const flameGeometry = new THREE.SphereGeometry(1.0, 8, 8);
590
+ const flameMaterial = new THREE.MeshBasicMaterial({
591
+ color: 0xffa500, // ๋…ธ๋ž€์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ
592
+ transparent: true,
593
+ opacity: 0.8
594
+ });
595
+ const flame = new THREE.Mesh(flameGeometry, flameMaterial);
596
+ flame.scale.set(2, 2, 3);
597
+ flashGroup.add(flame);
598
+
599
+ // ์—ฐ๊ธฐ ํšจ๊ณผ ํฌ๊ธฐ ์ฆ๊ฐ€
600
+ const smokeGeometry = new THREE.SphereGeometry(0.8, 8, 8);
601
+ const smokeMaterial = new THREE.MeshBasicMaterial({
602
+ color: 0x555555,
603
+ transparent: true,
604
+ opacity: 0.5
605
+ });
606
+
607
+ for (let i = 0; i < 5; i++) {
608
+ const smoke = new THREE.Mesh(smokeGeometry, smokeMaterial);
609
+ smoke.position.set(
610
+ Math.random() * 1 - 0.5,
611
+ Math.random() * 1 - 0.5,
612
+ -1 - Math.random()
613
+ );
614
+ smoke.scale.set(1.5, 1.5, 1.5);
615
+ flashGroup.add(smoke);
616
+ }
617
+
618
+ // ํฌ๊ตฌ ์œ„์น˜ ๊ณ„์‚ฐ
619
+ const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
620
+ const muzzlePosition = new THREE.Vector3();
621
+ const meshWorldQuaternion = new THREE.Quaternion();
622
+
623
+ this.mesh.getWorldPosition(muzzlePosition);
624
+ this.mesh.getWorldQuaternion(meshWorldQuaternion);
625
+
626
+ muzzleOffset.applyQuaternion(meshWorldQuaternion);
627
+ muzzlePosition.add(muzzleOffset);
628
+
629
+ flashGroup.position.copy(muzzlePosition);
630
+ flashGroup.quaternion.copy(meshWorldQuaternion);
631
+
632
+ this.scene.add(flashGroup);
633
+
634
+ // ์ดํŽ™ํŠธ ์ง€์† ์‹œ๊ฐ„ ์ฆ๊ฐ€
635
+ setTimeout(() => {
636
+ this.scene.remove(flashGroup);
637
+ }, 500);
638
+ }
639
+
640
 
641
  async initialize(loader) {
642
  try {
643
+ const modelPath = this.type === 'tank' ? '/models/t90.glb' : '/models/t90.glb';
644
+ const result = await loader.loadAsync(modelPath);
645
  this.mesh = result.scene;
646
  this.mesh.position.copy(this.position);
647
  this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
648
+
649
+ this.mesh.traverse((child) => {
650
+ if (child.isMesh) {
651
+ child.castShadow = true;
652
+ child.receiveShadow = true;
653
+ }
654
+ });
655
+
656
+ this.scene.add(this.mesh);
657
  this.isLoaded = true;
658
  } catch (error) {
659
  console.error('Error loading enemy model:', error);
 
661
  }
662
  }
663
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  update(playerPosition) {
665
+ if (!this.mesh || !this.isLoaded) return;
 
 
 
666
 
667
+ const direction = new THREE.Vector3()
668
+ .subVectors(playerPosition, this.mesh.position)
669
+ .normalize();
670
+
671
+ const distanceToPlayer = this.mesh.position.distanceTo(playerPosition);
672
+ const minDistance = 50;
673
+
674
+ // ์ด์ „ ์œ„์น˜ ์ €์žฅ
675
+ const previousPosition = this.mesh.position.clone();
676
+
677
+ if (distanceToPlayer > minDistance) {
678
+ const moveVector = direction.multiplyScalar(this.moveSpeed);
679
+ const newPosition = this.mesh.position.clone().add(moveVector);
680
+
681
+ // ์ง€ํ˜• ๋†’์ด ๊ฐ€์ ธ์˜ค๊ธฐ
682
+ const heightAtNewPos = window.gameInstance.getHeightAtPosition(
683
+ newPosition.x,
684
+ newPosition.z
685
+ );
686
+ newPosition.y = heightAtNewPos + TANK_HEIGHT;
687
+
688
+ // ์ž„์‹œ๋กœ ์œ„์น˜ ์ด๋™ํ•˜์—ฌ ์ถฉ๋Œ ์ฒดํฌ
689
+ const originalPosition = this.mesh.position.clone();
690
+ this.mesh.position.copy(newPosition);
691
+
692
+ // ์žฅ์• ๋ฌผ๊ณผ ์ถฉ๋Œ ์ฒดํฌ
693
+ const enemyBox = new THREE.Box3().setFromObject(this.mesh);
694
+ let hasCollision = false;
695
+
696
+ // ๋ชจ๋“  ์žฅ์• ๋ฌผ์— ๋Œ€ํ•ด ์ถฉ๋Œ ๊ฒ€์‚ฌ
697
+ for (const obstacle of window.gameInstance.obstacles) {
698
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
699
+ if (enemyBox.intersectsBox(obstacleBox)) {
700
+ hasCollision = true;
701
+ break;
702
+ }
703
+ }
704
+
705
+ // ๋‹ค๋ฅธ ์  ํƒฑํฌ์™€์˜ ์ถฉ๋Œ ๊ฒ€์‚ฌ
706
+ if (!hasCollision) {
707
+ for (const otherEnemy of window.gameInstance.enemies) {
708
+ if (otherEnemy !== this && otherEnemy.mesh) {
709
+ const otherEnemyBox = new THREE.Box3().setFromObject(otherEnemy.mesh);
710
+ if (enemyBox.intersectsBox(otherEnemyBox)) {
711
+ hasCollision = true;
712
+ break;
713
+ }
714
+ }
715
+ }
716
+ }
717
+
718
+ // ๋งต ๊ฒฝ๊ณ„ ์ฒดํฌ
719
+ const mapBoundary = MAP_SIZE / 2;
720
+ if (Math.abs(newPosition.x) > mapBoundary ||
721
+ Math.abs(newPosition.z) > mapBoundary) {
722
+ hasCollision = true;
723
+ }
724
+
725
+ // ์ถฉ๋Œ์ด ์žˆ์œผ๋ฉด ์ด์ „ ์œ„์น˜๋กœ ๋ณต๊ท€, ์—†์œผ๋ฉด ์ƒˆ ์œ„์น˜ ์œ ์ง€
726
+ if (hasCollision) {
727
+ this.mesh.position.copy(previousPosition);
728
+
729
+ // ์ถฉ๋Œ ์‹œ ์šฐํšŒ ๊ฒฝ๋กœ ๏ฟฝ๏ฟฝ๏ฟฝ๋„
730
+ const alternateDirections = [
731
+ new THREE.Vector3(-direction.z, 0, direction.x), // ์™ผ์ชฝ์œผ๋กœ 90๋„
732
+ new THREE.Vector3(direction.z, 0, -direction.x), // ์˜ค๋ฅธ์ชฝ์œผ๋กœ 90๋„
733
+ new THREE.Vector3(-direction.x, 0, -direction.z) // 180๋„ ํšŒ์ „
734
+ ];
735
+
736
+ for (const altDirection of alternateDirections) {
737
+ const altMoveVector = altDirection.multiplyScalar(this.moveSpeed);
738
+ const altNewPosition = previousPosition.clone().add(altMoveVector);
739
+
740
+ this.mesh.position.copy(altNewPosition);
741
+ const altEnemyBox = new THREE.Box3().setFromObject(this.mesh);
742
+
743
+ let altHasCollision = false;
744
+
745
+ // ์ƒˆ๋กœ์šด ๋ฐฉํ–ฅ์— ๋Œ€ํ•œ ์ถฉ๋Œ ๊ฒ€์‚ฌ
746
+ for (const obstacle of window.gameInstance.obstacles) {
747
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
748
+ if (altEnemyBox.intersectsBox(obstacleBox)) {
749
+ altHasCollision = true;
750
+ break;
751
+ }
752
+ }
753
+
754
+ if (!altHasCollision) {
755
+ // ์šฐํšŒ ๊ฒฝ๋กœ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฉด ๊ทธ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™
756
+ break;
757
+ } else {
758
+ // ์šฐํšŒ๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด ์ด์ „ ์œ„์น˜๋กœ ๋ณต๊ท€
759
+ this.mesh.position.copy(previousPosition);
760
+ }
761
+ }
762
+ }
763
+
764
+ // ์ง€ํ˜•์— ๋”ฐ๋ฅธ ๊ธฐ์šธ๊ธฐ ์กฐ์ •
765
+ const forwardVector = new THREE.Vector3(0, 0, 1).applyQuaternion(this.mesh.quaternion);
766
+ const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(this.mesh.quaternion);
767
+
768
+ const frontHeight = window.gameInstance.getHeightAtPosition(
769
+ this.mesh.position.x + forwardVector.x,
770
+ this.mesh.position.z + forwardVector.z
771
+ );
772
+ const backHeight = window.gameInstance.getHeightAtPosition(
773
+ this.mesh.position.x - forwardVector.x,
774
+ this.mesh.position.z - forwardVector.z
775
+ );
776
+ const rightHeight = window.gameInstance.getHeightAtPosition(
777
+ this.mesh.position.x + rightVector.x,
778
+ this.mesh.position.z + rightVector.z
779
+ );
780
+ const leftHeight = window.gameInstance.getHeightAtPosition(
781
+ this.mesh.position.x - rightVector.x,
782
+ this.mesh.position.z - rightVector.z
783
+ );
784
 
785
+ const pitch = Math.atan2(frontHeight - backHeight, 2);
786
+ const roll = Math.atan2(rightHeight - leftHeight, 2);
787
+
788
+ // ํ˜„์žฌ ํšŒ์ „ ์œ ์ง€ํ•˜๋ฉด์„œ ๊ธฐ์šธ๊ธฐ๋งŒ ์ ์šฉ
789
+ const currentRotation = this.mesh.rotation.y;
790
+ this.mesh.rotation.set(pitch, currentRotation, roll);
791
+ }
792
+
793
+ // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ํ–ฅํ•ด ํฌํƒ‘ ํšŒ์ „
794
+ this.mesh.lookAt(playerPosition);
795
+
796
+ // ์ด์•Œ ์—…๋ฐ์ดํŠธ
797
+ if (this.bullets) {
798
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
799
+ const bullet = this.bullets[i];
800
+ bullet.position.add(bullet.velocity);
801
+
802
+ // ์ด์•Œ์ด ๋งต ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€๊ฑฐ๋‚˜ ์žฅ์• ๋ฌผ๊ณผ ์ถฉ๋Œํ•˜๋ฉด ์ œ๊ฑฐ
803
+ if (Math.abs(bullet.position.x) > MAP_SIZE / 2 ||
804
+ Math.abs(bullet.position.z) > MAP_SIZE / 2) {
805
+ this.scene.remove(bullet);
806
+ this.bullets.splice(i, 1);
807
+ continue;
808
+ }
809
+
810
+ // ์ด์•Œ๊ณผ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
811
+ const bulletBox = new THREE.Box3().setFromObject(bullet);
812
+ for (const obstacle of window.gameInstance.obstacles) {
813
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
814
+ if (bulletBox.intersectsBox(obstacleBox)) {
815
+ this.scene.remove(bullet);
816
+ this.bullets.splice(i, 1);
817
+ break;
818
+ }
819
+ }
820
  }
 
821
  }
822
+ }
823
+
824
 
825
  shoot(playerPosition) {
826
+ const currentTime = Date.now();
827
+ const attackInterval = this.type === 'tank' ?
828
+ ENEMY_CONFIG.ATTACK_INTERVAL :
829
+ ENEMY_CONFIG.ATTACK_INTERVAL * 1.5;
830
 
831
+ if (currentTime - this.lastAttackTime < attackInterval) return;
 
 
 
832
 
833
+ // ๋ฐœ์‚ฌ ์ดํŽ™ํŠธ ์ƒ์„ฑ
834
+ this.createMuzzleFlash();
835
 
836
+ // ๋ฐœ์‚ฌ ์‚ฌ์šด๋“œ ์žฌ์ƒ
837
+ const enemyFireSound = new Audio('sounds/mbtfire5.ogg');
838
+ enemyFireSound.volume = 0.3;
839
+ enemyFireSound.play();
 
840
 
841
+ // ํฌํƒ„ ์ƒ์„ฑ (ํ”Œ๋ ˆ์ด์–ด์™€ ์œ ์‚ฌํ•˜๊ฒŒ ์ˆ˜์ •)
842
+ const bulletGeometry = new THREE.CylinderGeometry(0.2, 0.2, 2, 8);
843
+ const bulletMaterial = new THREE.MeshBasicMaterial({
844
+ color: 0xff0000, // ๋นจ๊ฐ„์ƒ‰ ํฌํƒ„
845
+ emissive: 0xff0000,
846
+ emissiveIntensity: 0.5
847
+ });
848
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
 
 
849
 
850
+ // ํฌ๊ตฌ ์œ„์น˜์—์„œ ๋ฐœ์‚ฌ
851
+ const muzzleOffset = new THREE.Vector3(0, 0.5, 4);
852
+ const muzzlePosition = new THREE.Vector3();
853
+ this.mesh.getWorldPosition(muzzlePosition);
854
+ muzzleOffset.applyQuaternion(this.mesh.quaternion);
855
+ muzzlePosition.add(muzzleOffset);
856
+
857
+ bullet.position.copy(muzzlePosition);
858
+
859
+ // ํฌํƒ„ ํšŒ์ „ ์„ค์ •
860
+ bullet.quaternion.copy(this.mesh.quaternion);
861
+
862
+ const direction = new THREE.Vector3()
863
+ .subVectors(playerPosition, muzzlePosition)
864
+ .normalize();
865
+
866
+ const bulletSpeed = this.type === 'tank' ?
867
+ ENEMY_CONFIG.BULLET_SPEED :
868
+ ENEMY_CONFIG.BULLET_SPEED * 0.8;
869
+
870
+ bullet.velocity = direction.multiplyScalar(bulletSpeed);
871
+
872
+ // ํฌํƒ„ ํŠธ๋ ˆ์ผ ํšจ๊ณผ ์ถ”๊ฐ€
873
+ const trailGeometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 8);
874
+ const trailMaterial = new THREE.MeshBasicMaterial({
875
+ color: 0xff4444,
876
+ transparent: true,
877
+ opacity: 0.5
878
+ });
879
+
880
+ const trail = new THREE.Mesh(trailGeometry, trailMaterial);
881
+ trail.position.z = -1;
882
+ bullet.add(trail);
883
+
884
+ this.scene.add(bullet);
885
+ this.bullets.push(bullet);
886
+ this.lastAttackTime = currentTime;
887
+ }
888
 
889
  takeDamage(damage) {
890
  this.health -= damage;
 
1718
  checkCollisions() {
1719
  if (this.isLoading || !this.tank.isLoaded) return;
1720
 
1721
+ // ๋ช…์ค‘ ์‚ฌ์šด๋“œ ๋ฐฐ์—ด ์ •์˜
1722
  const hitSounds = [
1723
  'sounds/hit1.ogg', 'sounds/hit2.ogg', 'sounds/hit3.ogg',
1724
  'sounds/hit4.ogg', 'sounds/hit5.ogg', 'sounds/hit6.ogg', 'sounds/hit7.ogg'
1725
  ];
1726
 
1727
+ // ํ”ผ๊ฒฉ ์‚ฌ์šด๋“œ ๋ฐฐ์—ด ์ •์˜
1728
  const beatSounds = ['sounds/beat1.ogg', 'sounds/beat2.ogg', 'sounds/beat3.ogg'];
1729
 
1730
  const tankPosition = this.tank.getPosition();
1731
+ const tankBoundingBox = new THREE.Box3().setFromObject(this.tank.body);
1732
+
1733
+ // ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ (๊ฐœ์„ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1734
  this.obstacles.forEach(obstacle => {
1735
+ if (obstacle.userData.isCollidable) { // ์ถฉ๋Œ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋งŒ ๊ฒ€์‚ฌ
1736
+ const obstacleBoundingBox = new THREE.Box3().setFromObject(obstacle);
1737
+ if (tankBoundingBox.intersectsBox(obstacleBoundingBox)) {
1738
+ this.tank.body.position.copy(this.previousTankPosition);
 
1739
  }
1740
+ }
1741
+ });
1742
+ // ์  ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ (์ถ”๊ฐ€)
1743
  this.enemies.forEach(enemy => {
1744
  if (!enemy.mesh || !enemy.isLoaded) return;
1745
 
1746
+ const enemyBoundingBox = new THREE.Box3().setFromObject(enemy.mesh);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1747
  const enemyPreviousPosition = enemy.mesh.position.clone();
1748
 
1749
  this.obstacles.forEach(obstacle => {
1750
+ const obstacleBoundingBox = new THREE.Box3().setFromObject(obstacle);
1751
+ if (enemyBoundingBox.intersectsBox(obstacleBoundingBox)) {
1752
  enemy.mesh.position.copy(enemyPreviousPosition);
1753
  }
1754
  });
 
1759
  if (!enemy.mesh || !enemy.isLoaded) return;
1760
 
1761
  enemy.bullets.forEach((bullet, bulletIndex) => {
1762
+ const distance = bullet.position.distanceTo(tankPosition);
1763
+ if (distance < 1) {
1764
+ // ํ”Œ๋ ˆ์ด์–ด ํ”ผ๊ฒฉ ์‚ฌ์šด๋“œ ์žฌ์ƒ
1765
  const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
1766
  const beatAudio = new Audio(randomBeatSound);
1767
  beatAudio.play();
 
1770
  this.endGame();
1771
  }
1772
  this.scene.remove(bullet);
1773
+ enemy.bullets.splice(bulletIndex, 1); // filter ๋Œ€์‹  splice ์‚ฌ์šฉ
1774
 
1775
+ this.createExplosion(bullet.position);
1776
  document.getElementById('health').style.width =
1777
  `${(this.tank.health / MAX_HEALTH) * 100}%`;
1778
  }
1779
  });
1780
  });
1781
 
1782
+ // ํ”Œ๋ ˆ์ด์–ด ํฌํƒ„
1783
+ // ํฌํƒ„๊ณผ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1784
+ for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
1785
+ const bullet = this.tank.bullets[i];
1786
+ const bulletBox = new THREE.Box3().setFromObject(bullet);
1787
 
1788
+ for (const obstacle of this.obstacles) {
1789
+ if (obstacle.userData.isCollidable) { // ์ถฉ๋Œ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋งŒ ๊ฒ€์‚ฌ
1790
+ const obstacleBox = new THREE.Box3().setFromObject(obstacle);
1791
+ if (bulletBox.intersectsBox(obstacleBox)) {
1792
+ // ํญ๋ฐœ ์ดํŽ™ํŠธ ์ƒ์„ฑ
1793
+ this.tank.createExplosionEffect(this.scene, bullet.position);
1794
+
1795
+ // ํฌํƒ„ ์ œ๊ฑฐ
1796
+ this.scene.remove(bullet);
1797
+ this.tank.bullets.splice(i, 1);
1798
+ break;
1799
  }
1800
  }
1801
  }
1802
+ }
1803
 
1804
+ // ์  ํƒฑํฌ์™€ ์žฅ์• ๋ฌผ ์ถฉ๋Œ ์ฒดํฌ
1805
+ this.enemies.forEach(enemy => {
1806
+ if (!enemy.mesh || !enemy.isLoaded) return;
1807
+
1808
+ enemy.bullets.forEach((bullet, bulletIndex) => {
1809
+ const distance = bullet.position.distanceTo(tankPosition);
1810
+ if (distance < 1) {
1811
+ // ํ”ผ๊ฒฉ ์‚ฌ์šด๋“œ ์žฌ์ƒ
1812
+ const randomBeatSound = beatSounds[Math.floor(Math.random() * beatSounds.length)];
1813
+ const beatAudio = new Audio(randomBeatSound);
1814
+ beatAudio.play();
1815
+
1816
+ if (this.tank.takeDamage(250)) {
1817
+ this.endGame();
1818
+ }
1819
 
1820
+ // ๊ธฐ์กด์˜ createExplosion ๋Œ€์‹  createExplosionEffect ์‚ฌ์šฉ
1821
+ this.tank.createExplosionEffect(this.scene, bullet.position);
1822
+
1823
+ this.scene.remove(bullet);
1824
+ enemy.bullets.splice(bulletIndex, 1);
1825
+
1826
+ document.getElementById('health').style.width =
1827
+ `${(this.tank.health / MAX_HEALTH) * 100}%`;
1828
+ }
1829
+ });
1830
+ });
 
 
1831
 
 
 
 
 
1832
 
1833
+ // ํ”Œ๋ ˆ์ด์–ด ์ด์•Œ๊ณผ ์  ์ถฉ๋Œ ์ฒดํฌ
1834
+ for (let i = this.tank.bullets.length - 1; i >= 0; i--) {
1835
+ const bullet = this.tank.bullets[i];
1836
+ for (let j = this.enemies.length - 1; j >= 0; j--) {
1837
+ const enemy = this.enemies[j];
1838
+ if (!enemy.mesh || !enemy.isLoaded) continue;
1839
+
1840
+ const distance = bullet.position.distanceTo(enemy.mesh.position);
1841
+ if (distance < 2) {
1842
+ const randomHitSound = hitSounds[Math.floor(Math.random() * hitSounds.length)];
1843
+ const hitAudio = new Audio(randomHitSound);
1844
+ hitAudio.play();
1845
+
1846
+ if (enemy.takeDamage(50)) {
1847
+ enemy.destroy();
1848
+ this.enemies.splice(j, 1);
1849
+ this.score += 100;
1850
+ document.getElementById('score').textContent = `Score: ${this.score}`;
1851
  }
1852
+
1853
+ // ์—ฌ๊ธฐ๋„ ๋™์ผํ•˜๊ฒŒ ์ˆ˜์ •
1854
+ this.tank.createExplosionEffect(this.scene, bullet.position);
1855
+
1856
+ this.scene.remove(bullet);
1857
+ this.tank.bullets.splice(i, 1);
1858
+ break;
1859
  }
1860
  }
1861
+ }
1862
 
1863
  // ํ”Œ๋ ˆ์ด์–ด ํƒฑํฌ์™€ ์  ์ „์ฐจ ์ถฉ๋Œ ์ฒดํฌ
1864
  this.enemies.forEach(enemy => {
1865
  if (!enemy.mesh || !enemy.isLoaded) return;
1866
 
1867
+ const enemyBoundingBox = new THREE.Box3().setFromObject(enemy.mesh);
1868
+ if (tankBoundingBox.intersectsBox(enemyBoundingBox)) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1869
  this.tank.body.position.copy(this.previousTankPosition);
1870
  }
1871
  });