Spaces:
Running
Running
Update game.js
Browse files
game.js
CHANGED
|
@@ -41,41 +41,48 @@ class TankPlayer {
|
|
| 41 |
}
|
| 42 |
|
| 43 |
async initialize(scene, loader) {
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
});
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
this.isLoaded = false;
|
| 77 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
}
|
|
|
|
| 79 |
|
| 80 |
shoot(scene) {
|
| 81 |
if (this.isReloading || this.ammo <= 0) return null;
|
|
@@ -488,73 +495,91 @@ class Game {
|
|
| 488 |
}
|
| 489 |
|
| 490 |
async initialize() {
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
| 517 |
-
ground.rotation.x = -Math.PI / 2;
|
| 518 |
-
ground.receiveShadow = true;
|
| 519 |
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
for (let i = 0; i < vertices.length; i += 3) {
|
| 524 |
-
vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
|
| 525 |
-
}
|
| 526 |
|
| 527 |
-
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
throw new Error('Tank loading failed');
|
| 536 |
-
}
|
| 537 |
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
tankPosition.z - 30
|
| 543 |
-
);
|
| 544 |
-
this.camera.lookAt(tankPosition);
|
| 545 |
-
|
| 546 |
-
this.isLoading = false;
|
| 547 |
-
document.getElementById('loading').style.display = 'none';
|
| 548 |
-
|
| 549 |
-
this.animate();
|
| 550 |
-
this.spawnEnemies();
|
| 551 |
-
this.startGameTimer();
|
| 552 |
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
}
|
|
|
|
| 558 |
|
| 559 |
// ๋ ์ด๋ ์
๋ฐ์ดํธ ๋ฉ์๋ ์ถ๊ฐ
|
| 560 |
updateRadar() {
|
|
@@ -669,6 +694,90 @@ class Game {
|
|
| 669 |
}
|
| 670 |
}
|
| 671 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
setupEventListeners() {
|
| 673 |
document.addEventListener('keydown', (event) => {
|
| 674 |
if (this.isLoading || this.isGameOver) return;
|
|
@@ -886,37 +995,48 @@ class Game {
|
|
| 886 |
}
|
| 887 |
|
| 888 |
getValidEnemySpawnPosition() {
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
|
|
|
| 900 |
|
| 901 |
-
|
| 902 |
-
|
| 903 |
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 912 |
|
| 913 |
-
|
| 914 |
|
| 915 |
-
|
| 916 |
-
|
|
|
|
|
|
|
|
|
|
| 917 |
|
| 918 |
-
|
| 919 |
-
|
|
|
|
|
|
|
|
|
|
| 920 |
updateParticles() {
|
| 921 |
for (let i = this.particles.length - 1; i >= 0; i--) {
|
| 922 |
const particle = this.particles[i];
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
async initialize(scene, loader) {
|
| 44 |
+
try {
|
| 45 |
+
const bodyResult = await loader.loadAsync('/models/abramsBody.glb');
|
| 46 |
+
this.body = bodyResult.scene;
|
| 47 |
+
|
| 48 |
+
const turretResult = await loader.loadAsync('/models/abramsTurret.glb');
|
| 49 |
+
this.turret = turretResult.scene;
|
| 50 |
+
|
| 51 |
+
this.turretGroup.position.y = 0.2;
|
| 52 |
+
this.turretGroup.add(this.turret);
|
| 53 |
+
this.body.add(this.turretGroup);
|
| 54 |
+
|
| 55 |
+
this.body.traverse((child) => {
|
| 56 |
+
if (child.isMesh) {
|
| 57 |
+
child.castShadow = true;
|
| 58 |
+
child.receiveShadow = true;
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
this.turret.traverse((child) => {
|
| 63 |
+
if (child.isMesh) {
|
| 64 |
+
child.castShadow = true;
|
| 65 |
+
child.receiveShadow = true;
|
| 66 |
+
}
|
| 67 |
+
});
|
|
|
|
| 68 |
|
| 69 |
+
// ์ฌ๊ธฐ์ ์ ํจํ ์คํฐ ์์น๋ฅผ ์ฐพ์ ์ ์ฉ
|
| 70 |
+
if (window.gameInstance) {
|
| 71 |
+
const spawnPos = window.gameInstance.findValidSpawnPosition();
|
| 72 |
+
this.body.position.copy(spawnPos);
|
| 73 |
+
} else {
|
| 74 |
+
this.body.position.copy(this.position);
|
|
|
|
| 75 |
}
|
| 76 |
+
|
| 77 |
+
scene.add(this.body);
|
| 78 |
+
this.isLoaded = true;
|
| 79 |
+
this.updateAmmoDisplay();
|
| 80 |
+
|
| 81 |
+
} catch (error) {
|
| 82 |
+
console.error('Error loading tank models:', error);
|
| 83 |
+
this.isLoaded = false;
|
| 84 |
}
|
| 85 |
+
}
|
| 86 |
|
| 87 |
shoot(scene) {
|
| 88 |
if (this.isReloading || this.ammo <= 0) return null;
|
|
|
|
| 495 |
}
|
| 496 |
|
| 497 |
async initialize() {
|
| 498 |
+
try {
|
| 499 |
+
// ์๊ฐ ํจ๊ณผ ์ ๊ฑฐ
|
| 500 |
+
this.scene.fog = null;
|
| 501 |
+
this.scene.background = new THREE.Color(0x87CEEB);
|
| 502 |
+
|
| 503 |
+
// ์ฃผ๋ณ๊ด ์ค์ - ๋ ๋ฐ๊ฒ
|
| 504 |
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
| 505 |
+
this.scene.add(ambientLight);
|
| 506 |
+
|
| 507 |
+
// ํ์๊ด ์ค์
|
| 508 |
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
| 509 |
+
directionalLight.position.set(100, 100, 50);
|
| 510 |
+
directionalLight.castShadow = true;
|
| 511 |
+
directionalLight.shadow.mapSize.width = 1024;
|
| 512 |
+
directionalLight.shadow.mapSize.height = 1024;
|
| 513 |
+
this.scene.add(directionalLight);
|
| 514 |
+
|
| 515 |
+
// ์งํ ์์ฑ ์์
|
| 516 |
+
const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 100, 100);
|
| 517 |
+
const groundMaterial = new THREE.MeshStandardMaterial({
|
| 518 |
+
color: 0xD2B48C,
|
| 519 |
+
roughness: 0.8,
|
| 520 |
+
metalness: 0.2
|
| 521 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
|
| 523 |
+
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
| 524 |
+
ground.rotation.x = -Math.PI / 2;
|
| 525 |
+
ground.receiveShadow = true;
|
|
|
|
|
|
|
|
|
|
| 526 |
|
| 527 |
+
// ๋ ์๋งํ ์งํ ์์ฑ
|
| 528 |
+
const vertices = ground.geometry.attributes.position.array;
|
| 529 |
+
const heightScale = 10; // ๋์ด ์ค์ผ์ผ ๊ฐ์
|
| 530 |
+
const frequency = 0.005; // ์ฃผํ์ ๊ฐ์๋ก ๋ ์๋งํ ๊ฒฝ์ฌ ์์ฑ
|
| 531 |
+
|
| 532 |
+
for (let i = 0; i < vertices.length; i += 3) {
|
| 533 |
+
const x = vertices[i];
|
| 534 |
+
const y = vertices[i + 1];
|
| 535 |
+
// Perlin ๋
ธ์ด์ฆ์ ์ ์ฌํ ํจ๊ณผ๋ฅผ ๋ด๋ ์์ ๋ ์์
|
| 536 |
+
vertices[i + 2] =
|
| 537 |
+
(Math.sin(x * frequency) * Math.cos(y * frequency) * heightScale) +
|
| 538 |
+
(Math.sin(x * frequency * 2) * Math.cos(y * frequency * 2) * heightScale * 0.5);
|
| 539 |
|
| 540 |
+
// ๋งต ๊ฐ์ฅ์๋ฆฌ๋ก ๊ฐ์๋ก ๋์ด๋ฅผ ์ ์ง์ ์ผ๋ก ์ค์
|
| 541 |
+
const distanceFromCenter = Math.sqrt(x * x + y * y) / (MAP_SIZE * 0.5);
|
| 542 |
+
const edgeFactor = Math.max(0, 1 - distanceFromCenter);
|
| 543 |
+
vertices[i + 2] *= edgeFactor;
|
| 544 |
+
}
|
|
|
|
|
|
|
| 545 |
|
| 546 |
+
ground.geometry.attributes.position.needsUpdate = true;
|
| 547 |
+
ground.geometry.computeVertexNormals();
|
| 548 |
+
this.ground = ground; // ์งํ ์ฐธ์กฐ ์ ์ฅ
|
| 549 |
+
this.scene.add(ground);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
+
// ์ฌ๋ง ์ฅ์ ์ถ๊ฐ
|
| 552 |
+
await this.addDesertDecorations();
|
| 553 |
+
|
| 554 |
+
// ํฑํฌ ์ด๊ธฐํ
|
| 555 |
+
await this.tank.initialize(this.scene, this.loader);
|
| 556 |
+
if (!this.tank.isLoaded) {
|
| 557 |
+
throw new Error('Tank loading failed');
|
| 558 |
}
|
| 559 |
+
|
| 560 |
+
// ์นด๋ฉ๋ผ ์ด๊ธฐ ์์น ์ค์
|
| 561 |
+
const tankPosition = this.tank.getPosition();
|
| 562 |
+
this.camera.position.set(
|
| 563 |
+
tankPosition.x,
|
| 564 |
+
tankPosition.y + 15,
|
| 565 |
+
tankPosition.z - 30
|
| 566 |
+
);
|
| 567 |
+
this.camera.lookAt(tankPosition);
|
| 568 |
+
|
| 569 |
+
// ๋ก๋ฉ ์๋ฃ
|
| 570 |
+
this.isLoading = false;
|
| 571 |
+
document.getElementById('loading').style.display = 'none';
|
| 572 |
+
|
| 573 |
+
// ๊ฒ์ ์์
|
| 574 |
+
this.animate();
|
| 575 |
+
this.spawnEnemies();
|
| 576 |
+
this.startGameTimer();
|
| 577 |
+
|
| 578 |
+
} catch (error) {
|
| 579 |
+
console.error('Game initialization error:', error);
|
| 580 |
+
this.handleLoadingError();
|
| 581 |
}
|
| 582 |
+
}
|
| 583 |
|
| 584 |
// ๋ ์ด๋ ์
๋ฐ์ดํธ ๋ฉ์๋ ์ถ๊ฐ
|
| 585 |
updateRadar() {
|
|
|
|
| 694 |
}
|
| 695 |
}
|
| 696 |
|
| 697 |
+
getHeightAtPosition(x, z) {
|
| 698 |
+
if (!this.ground) return 0;
|
| 699 |
+
|
| 700 |
+
// ์งํ์ ์ ์ ๋ฐ์ดํฐ
|
| 701 |
+
const vertices = this.ground.geometry.attributes.position.array;
|
| 702 |
+
const segmentsX = Math.sqrt(vertices.length / 3) - 1;
|
| 703 |
+
const segmentsZ = segmentsX;
|
| 704 |
+
|
| 705 |
+
// ๋งต ์ขํ๋ฅผ ์งํ ๊ฒฉ์ ์ขํ๋ก ๋ณํ
|
| 706 |
+
const gridX = ((x + MAP_SIZE / 2) / MAP_SIZE) * segmentsX;
|
| 707 |
+
const gridZ = ((z + MAP_SIZE / 2) / MAP_SIZE) * segmentsZ;
|
| 708 |
+
|
| 709 |
+
// ๊ฐ์ฅ ๊ฐ๊น์ด ๊ฒฉ์์ ์ฐพ๊ธฐ
|
| 710 |
+
const x1 = Math.floor(gridX);
|
| 711 |
+
const z1 = Math.floor(gridZ);
|
| 712 |
+
const x2 = Math.min(x1 + 1, segmentsX);
|
| 713 |
+
const z2 = Math.min(z1 + 1, segmentsZ);
|
| 714 |
+
|
| 715 |
+
// ๊ฒฉ์์ ๋ค์ ๋์ด ๊ฐ์ ธ์ค๊ธฐ
|
| 716 |
+
const getHeight = (x, z) => {
|
| 717 |
+
if (x < 0 || x > segmentsX || z < 0 || z > segmentsZ) return 0;
|
| 718 |
+
const index = (z * (segmentsX + 1) + x) * 3 + 2;
|
| 719 |
+
return vertices[index];
|
| 720 |
+
};
|
| 721 |
+
|
| 722 |
+
const h11 = getHeight(x1, z1);
|
| 723 |
+
const h21 = getHeight(x2, z1);
|
| 724 |
+
const h12 = getHeight(x1, z2);
|
| 725 |
+
const h22 = getHeight(x2, z2);
|
| 726 |
+
|
| 727 |
+
// ๋ณด๊ฐ์ผ๋ก ์ ํํ ๋์ด ๊ณ์ฐ
|
| 728 |
+
const fx = gridX - x1;
|
| 729 |
+
const fz = gridZ - z1;
|
| 730 |
+
|
| 731 |
+
const h1 = h11 * (1 - fx) + h21 * fx;
|
| 732 |
+
const h2 = h12 * (1 - fx) + h22 * fx;
|
| 733 |
+
|
| 734 |
+
return h1 * (1 - fz) + h2 * fz;
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
findValidSpawnPosition() {
|
| 738 |
+
const margin = 50;
|
| 739 |
+
let position;
|
| 740 |
+
let attempts = 0;
|
| 741 |
+
const maxAttempts = 50;
|
| 742 |
+
const maxSlope = 0.3; // ์ต๋ ํ์ฉ ๊ฒฝ์ฌ
|
| 743 |
+
|
| 744 |
+
while (attempts < maxAttempts) {
|
| 745 |
+
position = new THREE.Vector3(
|
| 746 |
+
(Math.random() - 0.5) * (MAP_SIZE - margin * 2),
|
| 747 |
+
0,
|
| 748 |
+
(Math.random() - 0.5) * (MAP_SIZE - margin * 2)
|
| 749 |
+
);
|
| 750 |
+
|
| 751 |
+
// ํ์ฌ ์์น์ ๋์ด ๊ฐ์ ธ์ค๊ธฐ
|
| 752 |
+
const height = this.getHeightAtPosition(position.x, position.z);
|
| 753 |
+
position.y = height + TANK_HEIGHT;
|
| 754 |
+
|
| 755 |
+
// ์ฃผ๋ณ ์งํ์ ๊ฒฝ์ฌ ์ฒดํฌ
|
| 756 |
+
const checkPoints = [
|
| 757 |
+
{ x: position.x + 2, z: position.z },
|
| 758 |
+
{ x: position.x - 2, z: position.z },
|
| 759 |
+
{ x: position.x, z: position.z + 2 },
|
| 760 |
+
{ x: position.x, z: position.z - 2 }
|
| 761 |
+
];
|
| 762 |
+
|
| 763 |
+
const slopes = checkPoints.map(point => {
|
| 764 |
+
const pointHeight = this.getHeightAtPosition(point.x, point.z);
|
| 765 |
+
return Math.abs(pointHeight - height) / 2;
|
| 766 |
+
});
|
| 767 |
+
|
| 768 |
+
const maxCurrentSlope = Math.max(...slopes);
|
| 769 |
+
|
| 770 |
+
if (maxCurrentSlope <= maxSlope) {
|
| 771 |
+
return position;
|
| 772 |
+
}
|
| 773 |
+
|
| 774 |
+
attempts++;
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
// ์คํจ ์ ๊ธฐ๋ณธ ์์น ๋ฐํ
|
| 778 |
+
return new THREE.Vector3(0, TANK_HEIGHT, 0);
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
setupEventListeners() {
|
| 782 |
document.addEventListener('keydown', (event) => {
|
| 783 |
if (this.isLoading || this.isGameOver) return;
|
|
|
|
| 995 |
}
|
| 996 |
|
| 997 |
getValidEnemySpawnPosition() {
|
| 998 |
+
const margin = 50;
|
| 999 |
+
let position;
|
| 1000 |
+
let attempts = 0;
|
| 1001 |
+
const maxAttempts = 50;
|
| 1002 |
+
const maxSlope = 0.3;
|
| 1003 |
+
|
| 1004 |
+
do {
|
| 1005 |
+
position = new THREE.Vector3(
|
| 1006 |
+
(Math.random() - 0.5) * (MAP_SIZE - margin * 2),
|
| 1007 |
+
0,
|
| 1008 |
+
(Math.random() - 0.5) * (MAP_SIZE - margin * 2)
|
| 1009 |
+
);
|
| 1010 |
|
| 1011 |
+
const height = this.getHeightAtPosition(position.x, position.z);
|
| 1012 |
+
position.y = height + TANK_HEIGHT;
|
| 1013 |
|
| 1014 |
+
// ์ฃผ๋ณ ๊ฒฝ์ฌ ์ฒดํฌ
|
| 1015 |
+
const checkPoints = [
|
| 1016 |
+
{ x: position.x + 2, z: position.z },
|
| 1017 |
+
{ x: position.x - 2, z: position.z },
|
| 1018 |
+
{ x: position.x, z: position.z + 2 },
|
| 1019 |
+
{ x: position.x, z: position.z - 2 }
|
| 1020 |
+
];
|
| 1021 |
+
|
| 1022 |
+
const slopes = checkPoints.map(point => {
|
| 1023 |
+
const pointHeight = this.getHeightAtPosition(point.x, point.z);
|
| 1024 |
+
return Math.abs(pointHeight - height) / 2;
|
| 1025 |
+
});
|
| 1026 |
|
| 1027 |
+
const maxCurrentSlope = Math.max(...slopes);
|
| 1028 |
|
| 1029 |
+
// ํ๋ ์ด์ด์์ ๊ฑฐ๋ฆฌ ์ฒดํฌ
|
| 1030 |
+
const distanceToPlayer = position.distanceTo(this.tank.getPosition());
|
| 1031 |
+
if (distanceToPlayer > 100 && maxCurrentSlope <= maxSlope) {
|
| 1032 |
+
return position;
|
| 1033 |
+
}
|
| 1034 |
|
| 1035 |
+
attempts++;
|
| 1036 |
+
} while (attempts < maxAttempts);
|
| 1037 |
+
|
| 1038 |
+
return null;
|
| 1039 |
+
}
|
| 1040 |
updateParticles() {
|
| 1041 |
for (let i = this.particles.length - 1; i >= 0; i--) {
|
| 1042 |
const particle = this.particles[i];
|