Spaces:
Running
Running
cutechicken
commited on
Commit
โข
6fd82de
1
Parent(s):
328bda1
Update game.js
Browse files
game.js
CHANGED
@@ -5,7 +5,7 @@ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.j
|
|
5 |
// ๊ฒ์ ์์
|
6 |
const GAME_DURATION = 180;
|
7 |
const MAP_SIZE = 2000;
|
8 |
-
const
|
9 |
const ENEMY_GROUND_HEIGHT = 0;
|
10 |
const ENEMY_SCALE = 10;
|
11 |
const MAX_HEALTH = 1000;
|
@@ -19,8 +19,89 @@ const ENEMY_CONFIG = {
|
|
19 |
BULLET_SPEED: 2
|
20 |
};
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
// ๊ฒ์ ๋ณ์
|
23 |
let scene, camera, renderer, controls;
|
|
|
24 |
let enemies = [];
|
25 |
let bullets = [];
|
26 |
let enemyBullets = [];
|
@@ -30,7 +111,6 @@ let currentStage = 1;
|
|
30 |
let isGameOver = false;
|
31 |
let lastTime = performance.now();
|
32 |
let lastRender = 0;
|
33 |
-
|
34 |
// ์ค์ค๋ ์ดํฐ ๊ธฐ๋ฐ ์ด์๋ฆฌ ์์ฑ๊ธฐ
|
35 |
class GunSoundGenerator {
|
36 |
constructor() {
|
@@ -110,7 +190,7 @@ async function init() {
|
|
110 |
|
111 |
// Camera ์ค์
|
112 |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
113 |
-
camera.position.set(0,
|
114 |
|
115 |
// ๊ธฐ๋ณธ ์กฐ๋ช
|
116 |
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
|
@@ -128,9 +208,10 @@ async function init() {
|
|
128 |
// ์ด๋ฒคํธ ๋ฆฌ์ค๋
|
129 |
setupEventListeners();
|
130 |
|
131 |
-
//
|
132 |
-
|
133 |
-
|
|
|
134 |
// ๊ฒ์ ์์ ์ด๊ธฐํ
|
135 |
await Promise.all([
|
136 |
createTerrain(),
|
@@ -154,9 +235,18 @@ function setupEventListeners() {
|
|
154 |
document.addEventListener('click', onClick);
|
155 |
document.addEventListener('keydown', onKeyDown);
|
156 |
document.addEventListener('keyup', onKeyUp);
|
|
|
157 |
window.addEventListener('resize', onWindowResize);
|
158 |
}
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
async function testModelLoading() {
|
161 |
const loader = new GLTFLoader();
|
162 |
try {
|
@@ -241,7 +331,6 @@ async function createEnemies() {
|
|
241 |
scene.add(tempEnemy.model);
|
242 |
enemies.push(tempEnemy);
|
243 |
|
244 |
-
// GLB ๋ชจ๋ธ ๋ก๋
|
245 |
try {
|
246 |
const modelIndex = i % 4 + 1;
|
247 |
const modelPath = `models/enemy${modelIndex}.glb`;
|
@@ -250,11 +339,9 @@ async function createEnemies() {
|
|
250 |
const gltf = await loader.loadAsync(modelPath);
|
251 |
const model = gltf.scene;
|
252 |
|
253 |
-
// ๋ชจ๋ธ ์ค์
|
254 |
model.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
|
255 |
model.position.copy(position);
|
256 |
|
257 |
-
// ๋ชจ๋ธ ์ฌ์ง ๋ฐ ๊ทธ๋ฆผ์ ์ค์
|
258 |
model.traverse((node) => {
|
259 |
if (node.isMesh) {
|
260 |
node.castShadow = true;
|
@@ -264,7 +351,6 @@ async function createEnemies() {
|
|
264 |
}
|
265 |
});
|
266 |
|
267 |
-
// ์์ ๋ชจ๋ธ ๊ต์ฒด
|
268 |
scene.remove(tempEnemy.model);
|
269 |
scene.add(model);
|
270 |
enemies[enemies.indexOf(tempEnemy)].model = model;
|
@@ -296,7 +382,6 @@ function createTemporaryEnemy(position) {
|
|
296 |
lastAttackTime: 0
|
297 |
};
|
298 |
}
|
299 |
-
|
300 |
function createExplosion(position) {
|
301 |
const particles = [];
|
302 |
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
@@ -355,11 +440,27 @@ function onClick() {
|
|
355 |
}
|
356 |
|
357 |
function onKeyDown(event) {
|
|
|
|
|
358 |
switch(event.code) {
|
359 |
-
case 'KeyW':
|
360 |
-
|
361 |
-
|
362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
363 |
case 'KeyR': reload(); break;
|
364 |
}
|
365 |
}
|
@@ -388,7 +489,7 @@ const moveState = {
|
|
388 |
};
|
389 |
|
390 |
function shoot() {
|
391 |
-
if (ammo <= 0) return;
|
392 |
|
393 |
ammo--;
|
394 |
updateAmmoDisplay();
|
@@ -400,7 +501,7 @@ function shoot() {
|
|
400 |
|
401 |
// ์ด๊ตฌ ํ์ผ ํจ๊ณผ
|
402 |
const muzzleFlash = new THREE.PointLight(0xffff00, 3, 10);
|
403 |
-
muzzleFlash.position.copy(
|
404 |
scene.add(muzzleFlash);
|
405 |
setTimeout(() => scene.remove(muzzleFlash), 50);
|
406 |
}
|
@@ -415,15 +516,15 @@ function createBullet() {
|
|
415 |
})
|
416 |
);
|
417 |
|
418 |
-
|
419 |
-
|
420 |
-
|
|
|
421 |
bullet.velocity = direction.multiplyScalar(5);
|
422 |
|
423 |
scene.add(bullet);
|
424 |
return bullet;
|
425 |
}
|
426 |
-
|
427 |
function createEnemyBullet(enemy) {
|
428 |
const bullet = new THREE.Mesh(
|
429 |
new THREE.SphereGeometry(0.5),
|
@@ -438,7 +539,7 @@ function createEnemyBullet(enemy) {
|
|
438 |
bullet.position.y += 5;
|
439 |
|
440 |
const direction = new THREE.Vector3();
|
441 |
-
direction.subVectors(
|
442 |
bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
|
443 |
|
444 |
scene.add(bullet);
|
@@ -446,19 +547,14 @@ function createEnemyBullet(enemy) {
|
|
446 |
}
|
447 |
|
448 |
function updateMovement() {
|
449 |
-
if (controls.isLocked) {
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
if (camera.position.y < HELICOPTER_HEIGHT) {
|
458 |
-
camera.position.y = HELICOPTER_HEIGHT;
|
459 |
-
} else if (camera.position.y > HELICOPTER_HEIGHT + 10) {
|
460 |
-
camera.position.y = HELICOPTER_HEIGHT + 10;
|
461 |
-
}
|
462 |
}
|
463 |
}
|
464 |
|
@@ -490,7 +586,7 @@ function updateBullets() {
|
|
490 |
}
|
491 |
|
492 |
// ๋ฒ์ ๋ฒ์ด๋ ์ด์ ์ ๊ฑฐ
|
493 |
-
if (bullets[i] && bullets[i].position.distanceTo(
|
494 |
scene.remove(bullets[i]);
|
495 |
bullets.splice(i, 1);
|
496 |
}
|
@@ -503,7 +599,7 @@ function updateEnemyBullets() {
|
|
503 |
|
504 |
enemyBullets[i].position.add(enemyBullets[i].velocity);
|
505 |
|
506 |
-
if (enemyBullets[i].position.distanceTo(
|
507 |
playerHealth -= 10;
|
508 |
updateHealthBar();
|
509 |
createExplosion(enemyBullets[i].position.clone());
|
@@ -516,7 +612,7 @@ function updateEnemyBullets() {
|
|
516 |
continue;
|
517 |
}
|
518 |
|
519 |
-
if (enemyBullets[i].position.distanceTo(
|
520 |
scene.remove(enemyBullets[i]);
|
521 |
enemyBullets.splice(i, 1);
|
522 |
}
|
@@ -531,7 +627,7 @@ function updateEnemies() {
|
|
531 |
|
532 |
// ์ ์ด๋ ๋ก์ง
|
533 |
const direction = new THREE.Vector3();
|
534 |
-
direction.subVectors(
|
535 |
direction.y = 0;
|
536 |
direction.normalize();
|
537 |
|
@@ -542,13 +638,13 @@ function updateEnemies() {
|
|
542 |
|
543 |
// ์ ์ด ํ๋ ์ด์ด๋ฅผ ๋ฐ๋ผ๋ณด๋๋ก ์ค์
|
544 |
enemy.model.lookAt(new THREE.Vector3(
|
545 |
-
|
546 |
enemy.model.position.y,
|
547 |
-
|
548 |
));
|
549 |
|
550 |
// ๊ณต๊ฒฉ ๋ก์ง
|
551 |
-
const distanceToPlayer = enemy.model.position.distanceTo(
|
552 |
if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
|
553 |
currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
|
554 |
|
@@ -563,7 +659,6 @@ function updateEnemies() {
|
|
563 |
}
|
564 |
});
|
565 |
}
|
566 |
-
|
567 |
function reload() {
|
568 |
ammo = 30;
|
569 |
updateAmmoDisplay();
|
@@ -579,9 +674,11 @@ function updateHealthBar() {
|
|
579 |
healthElement.style.width = `${healthPercentage}%`;
|
580 |
}
|
581 |
|
582 |
-
function
|
|
|
|
|
583 |
document.querySelector('#altitude-indicator span').textContent =
|
584 |
-
Math.round(
|
585 |
|
586 |
const speed = Math.round(
|
587 |
Math.sqrt(
|
@@ -592,7 +689,7 @@ function updateHelicopterHUD() {
|
|
592 |
document.querySelector('#speed-indicator span').textContent = speed;
|
593 |
|
594 |
const heading = Math.round(
|
595 |
-
(
|
596 |
);
|
597 |
document.querySelector('#compass span').textContent = heading;
|
598 |
|
@@ -600,17 +697,19 @@ function updateHelicopterHUD() {
|
|
600 |
}
|
601 |
|
602 |
function updateRadar() {
|
|
|
|
|
603 |
const radarTargets = document.querySelector('.radar-targets');
|
604 |
radarTargets.innerHTML = '';
|
605 |
|
606 |
enemies.forEach(enemy => {
|
607 |
if (!enemy || !enemy.model) return;
|
608 |
|
609 |
-
const relativePos = enemy.model.position.clone().sub(
|
610 |
const distance = relativePos.length();
|
611 |
|
612 |
if (distance < 500) {
|
613 |
-
const playerAngle =
|
614 |
const enemyAngle = Math.atan2(relativePos.x, relativePos.z);
|
615 |
const relativeAngle = enemyAngle - playerAngle;
|
616 |
|
@@ -665,7 +764,6 @@ function gameOver(won) {
|
|
665 |
function gameLoop(timestamp) {
|
666 |
requestAnimationFrame(gameLoop);
|
667 |
|
668 |
-
// ํ๋ ์ ์ ํ (60fps)
|
669 |
if (timestamp - lastRender < 16) {
|
670 |
return;
|
671 |
}
|
@@ -676,7 +774,7 @@ function gameLoop(timestamp) {
|
|
676 |
updateBullets();
|
677 |
updateEnemies();
|
678 |
updateEnemyBullets();
|
679 |
-
|
680 |
checkGameStatus();
|
681 |
}
|
682 |
|
@@ -725,5 +823,6 @@ window.debugGame = {
|
|
725 |
camera,
|
726 |
enemies,
|
727 |
gunSound,
|
|
|
728 |
reloadEnemies: createEnemies
|
729 |
};
|
|
|
5 |
// ๊ฒ์ ์์
|
6 |
const GAME_DURATION = 180;
|
7 |
const MAP_SIZE = 2000;
|
8 |
+
const TANK_HEIGHT = 2.0; // ์ ์ฐจ ๋์ด๋ก ๋ณ๊ฒฝ
|
9 |
const ENEMY_GROUND_HEIGHT = 0;
|
10 |
const ENEMY_SCALE = 10;
|
11 |
const MAX_HEALTH = 1000;
|
|
|
19 |
BULLET_SPEED: 2
|
20 |
};
|
21 |
|
22 |
+
// TankPlayer ํด๋์ค ์ ์
|
23 |
+
class TankPlayer {
|
24 |
+
constructor() {
|
25 |
+
this.body = null; // ์ ์ฐจ ๋ชธ์ฒด
|
26 |
+
this.turret = null; // ์ ์ฐจ ํฌํ
|
27 |
+
this.position = new THREE.Vector3(0, 0, 0);
|
28 |
+
this.rotation = new THREE.Euler(0, 0, 0);
|
29 |
+
this.turretRotation = 0;
|
30 |
+
this.moveSpeed = 0.5;
|
31 |
+
this.turnSpeed = 0.03;
|
32 |
+
}
|
33 |
+
|
34 |
+
async initialize(scene, loader) {
|
35 |
+
try {
|
36 |
+
// ๋ชธ์ฒด ๋ก๋
|
37 |
+
const bodyResult = await loader.loadAsync('/models/abramsBody.glb');
|
38 |
+
this.body = bodyResult.scene;
|
39 |
+
this.body.position.copy(this.position);
|
40 |
+
|
41 |
+
// ํฌํ ๋ก๋
|
42 |
+
const turretResult = await loader.loadAsync('/models/abramsTurret.glb');
|
43 |
+
this.turret = turretResult.scene;
|
44 |
+
|
45 |
+
// ํฌํ ์์น ์กฐ์ (๋ชธ์ฒด ์์ ์ฌ๋ฆผ)
|
46 |
+
this.turret.position.y = TANK_HEIGHT;
|
47 |
+
|
48 |
+
// ๊ทธ๋ฆผ์ ์ค์
|
49 |
+
this.body.traverse((child) => {
|
50 |
+
if (child.isMesh) {
|
51 |
+
child.castShadow = true;
|
52 |
+
child.receiveShadow = true;
|
53 |
+
}
|
54 |
+
});
|
55 |
+
|
56 |
+
this.turret.traverse((child) => {
|
57 |
+
if (child.isMesh) {
|
58 |
+
child.castShadow = true;
|
59 |
+
child.receiveShadow = true;
|
60 |
+
}
|
61 |
+
});
|
62 |
+
|
63 |
+
// ์ฌ์ ์ถ๊ฐ
|
64 |
+
this.body.add(this.turret);
|
65 |
+
scene.add(this.body);
|
66 |
+
|
67 |
+
} catch (error) {
|
68 |
+
console.error('Error loading tank models:', error);
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
update(mouseX, mouseY) {
|
73 |
+
if (!this.body || !this.turret) return;
|
74 |
+
|
75 |
+
// ๋ง์ฐ์ค ์์น๋ฅผ ์ด์ฉํ ํฌํ ํ์ ๊ณ์ฐ
|
76 |
+
const mousePosition = new THREE.Vector2(mouseX, mouseY);
|
77 |
+
const targetAngle = Math.atan2(mousePosition.x, mousePosition.y);
|
78 |
+
|
79 |
+
// ํฌํ ๋ถ๋๋ฌ์ด ํ์
|
80 |
+
const currentRotation = this.turret.rotation.y;
|
81 |
+
const rotationDiff = targetAngle - currentRotation;
|
82 |
+
this.turret.rotation.y += rotationDiff * 0.1;
|
83 |
+
}
|
84 |
+
|
85 |
+
move(direction) {
|
86 |
+
if (!this.body) return;
|
87 |
+
|
88 |
+
const moveVector = new THREE.Vector3();
|
89 |
+
moveVector.x = direction.x * this.moveSpeed;
|
90 |
+
moveVector.z = direction.z * this.moveSpeed;
|
91 |
+
|
92 |
+
moveVector.applyEuler(this.body.rotation);
|
93 |
+
this.body.position.add(moveVector);
|
94 |
+
}
|
95 |
+
|
96 |
+
rotate(angle) {
|
97 |
+
if (!this.body) return;
|
98 |
+
this.body.rotation.y += angle * this.turnSpeed;
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
// ๊ฒ์ ๋ณ์
|
103 |
let scene, camera, renderer, controls;
|
104 |
+
let player; // ์ ์ฐจ ํ๋ ์ด์ด ์ธ์คํด์ค
|
105 |
let enemies = [];
|
106 |
let bullets = [];
|
107 |
let enemyBullets = [];
|
|
|
111 |
let isGameOver = false;
|
112 |
let lastTime = performance.now();
|
113 |
let lastRender = 0;
|
|
|
114 |
// ์ค์ค๋ ์ดํฐ ๊ธฐ๋ฐ ์ด์๋ฆฌ ์์ฑ๊ธฐ
|
115 |
class GunSoundGenerator {
|
116 |
constructor() {
|
|
|
190 |
|
191 |
// Camera ์ค์
|
192 |
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
193 |
+
camera.position.set(0, TANK_HEIGHT + 5, -10); // ์นด๋ฉ๋ผ ์์น ์ ์ฐจ์ ๋ง๊ฒ ์กฐ์
|
194 |
|
195 |
// ๊ธฐ๋ณธ ์กฐ๋ช
|
196 |
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
|
|
|
208 |
// ์ด๋ฒคํธ ๋ฆฌ์ค๋
|
209 |
setupEventListeners();
|
210 |
|
211 |
+
// ํ๋ ์ด์ด ์ด๊ธฐํ
|
212 |
+
player = new TankPlayer();
|
213 |
+
await player.initialize(scene, new GLTFLoader());
|
214 |
+
|
215 |
// ๊ฒ์ ์์ ์ด๊ธฐํ
|
216 |
await Promise.all([
|
217 |
createTerrain(),
|
|
|
235 |
document.addEventListener('click', onClick);
|
236 |
document.addEventListener('keydown', onKeyDown);
|
237 |
document.addEventListener('keyup', onKeyUp);
|
238 |
+
document.addEventListener('mousemove', onMouseMove);
|
239 |
window.addEventListener('resize', onWindowResize);
|
240 |
}
|
241 |
|
242 |
+
// ๋ง์ฐ์ค ์ด๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ถ๊ฐ
|
243 |
+
function onMouseMove(event) {
|
244 |
+
if (controls.isLocked && player) {
|
245 |
+
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
|
246 |
+
const mouseY = -(event.clientY / window.innerHeight) * 2 + 1;
|
247 |
+
player.update(mouseX, mouseY);
|
248 |
+
}
|
249 |
+
}
|
250 |
async function testModelLoading() {
|
251 |
const loader = new GLTFLoader();
|
252 |
try {
|
|
|
331 |
scene.add(tempEnemy.model);
|
332 |
enemies.push(tempEnemy);
|
333 |
|
|
|
334 |
try {
|
335 |
const modelIndex = i % 4 + 1;
|
336 |
const modelPath = `models/enemy${modelIndex}.glb`;
|
|
|
339 |
const gltf = await loader.loadAsync(modelPath);
|
340 |
const model = gltf.scene;
|
341 |
|
|
|
342 |
model.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
|
343 |
model.position.copy(position);
|
344 |
|
|
|
345 |
model.traverse((node) => {
|
346 |
if (node.isMesh) {
|
347 |
node.castShadow = true;
|
|
|
351 |
}
|
352 |
});
|
353 |
|
|
|
354 |
scene.remove(tempEnemy.model);
|
355 |
scene.add(model);
|
356 |
enemies[enemies.indexOf(tempEnemy)].model = model;
|
|
|
382 |
lastAttackTime: 0
|
383 |
};
|
384 |
}
|
|
|
385 |
function createExplosion(position) {
|
386 |
const particles = [];
|
387 |
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
|
440 |
}
|
441 |
|
442 |
function onKeyDown(event) {
|
443 |
+
if (!player) return;
|
444 |
+
|
445 |
switch(event.code) {
|
446 |
+
case 'KeyW':
|
447 |
+
moveState.forward = true;
|
448 |
+
const forwardDir = new THREE.Vector3(0, 0, -1);
|
449 |
+
player.move(forwardDir);
|
450 |
+
break;
|
451 |
+
case 'KeyS':
|
452 |
+
moveState.backward = true;
|
453 |
+
const backwardDir = new THREE.Vector3(0, 0, 1);
|
454 |
+
player.move(backwardDir);
|
455 |
+
break;
|
456 |
+
case 'KeyA':
|
457 |
+
moveState.left = true;
|
458 |
+
player.rotate(1);
|
459 |
+
break;
|
460 |
+
case 'KeyD':
|
461 |
+
moveState.right = true;
|
462 |
+
player.rotate(-1);
|
463 |
+
break;
|
464 |
case 'KeyR': reload(); break;
|
465 |
}
|
466 |
}
|
|
|
489 |
};
|
490 |
|
491 |
function shoot() {
|
492 |
+
if (ammo <= 0 || !player || !player.turret) return;
|
493 |
|
494 |
ammo--;
|
495 |
updateAmmoDisplay();
|
|
|
501 |
|
502 |
// ์ด๊ตฌ ํ์ผ ํจ๊ณผ
|
503 |
const muzzleFlash = new THREE.PointLight(0xffff00, 3, 10);
|
504 |
+
muzzleFlash.position.copy(player.turret.position);
|
505 |
scene.add(muzzleFlash);
|
506 |
setTimeout(() => scene.remove(muzzleFlash), 50);
|
507 |
}
|
|
|
516 |
})
|
517 |
);
|
518 |
|
519 |
+
// ํฌํ ์์น์์ ์ด์ ๋ฐ์ฌ
|
520 |
+
bullet.position.copy(player.turret.position);
|
521 |
+
const direction = new THREE.Vector3(0, 0, -1);
|
522 |
+
direction.applyQuaternion(player.turret.quaternion);
|
523 |
bullet.velocity = direction.multiplyScalar(5);
|
524 |
|
525 |
scene.add(bullet);
|
526 |
return bullet;
|
527 |
}
|
|
|
528 |
function createEnemyBullet(enemy) {
|
529 |
const bullet = new THREE.Mesh(
|
530 |
new THREE.SphereGeometry(0.5),
|
|
|
539 |
bullet.position.y += 5;
|
540 |
|
541 |
const direction = new THREE.Vector3();
|
542 |
+
direction.subVectors(player.body.position, enemy.model.position).normalize();
|
543 |
bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
|
544 |
|
545 |
scene.add(bullet);
|
|
|
547 |
}
|
548 |
|
549 |
function updateMovement() {
|
550 |
+
if (controls.isLocked && !isGameOver && player) {
|
551 |
+
// ์นด๋ฉ๋ผ ์์น ์
๋ฐ์ดํธ
|
552 |
+
const cameraOffset = new THREE.Vector3(0, TANK_HEIGHT + 5, -15);
|
553 |
+
cameraOffset.applyQuaternion(player.body.quaternion);
|
554 |
+
camera.position.copy(player.body.position).add(cameraOffset);
|
555 |
+
|
556 |
+
// ์นด๋ฉ๋ผ๊ฐ ํ๋ ์ด์ด๋ฅผ ๋ฐ๋ผ๋ณด๋๋ก ์ค์
|
557 |
+
camera.lookAt(player.body.position);
|
|
|
|
|
|
|
|
|
|
|
558 |
}
|
559 |
}
|
560 |
|
|
|
586 |
}
|
587 |
|
588 |
// ๋ฒ์ ๋ฒ์ด๋ ์ด์ ์ ๊ฑฐ
|
589 |
+
if (bullets[i] && bullets[i].position.distanceTo(player.body.position) > 1000) {
|
590 |
scene.remove(bullets[i]);
|
591 |
bullets.splice(i, 1);
|
592 |
}
|
|
|
599 |
|
600 |
enemyBullets[i].position.add(enemyBullets[i].velocity);
|
601 |
|
602 |
+
if (enemyBullets[i].position.distanceTo(player.body.position) < 3) {
|
603 |
playerHealth -= 10;
|
604 |
updateHealthBar();
|
605 |
createExplosion(enemyBullets[i].position.clone());
|
|
|
612 |
continue;
|
613 |
}
|
614 |
|
615 |
+
if (enemyBullets[i].position.distanceTo(player.body.position) > 1000) {
|
616 |
scene.remove(enemyBullets[i]);
|
617 |
enemyBullets.splice(i, 1);
|
618 |
}
|
|
|
627 |
|
628 |
// ์ ์ด๋ ๋ก์ง
|
629 |
const direction = new THREE.Vector3();
|
630 |
+
direction.subVectors(player.body.position, enemy.model.position);
|
631 |
direction.y = 0;
|
632 |
direction.normalize();
|
633 |
|
|
|
638 |
|
639 |
// ์ ์ด ํ๋ ์ด์ด๋ฅผ ๋ฐ๋ผ๋ณด๋๋ก ์ค์
|
640 |
enemy.model.lookAt(new THREE.Vector3(
|
641 |
+
player.body.position.x,
|
642 |
enemy.model.position.y,
|
643 |
+
player.body.position.z
|
644 |
));
|
645 |
|
646 |
// ๊ณต๊ฒฉ ๋ก์ง
|
647 |
+
const distanceToPlayer = enemy.model.position.distanceTo(player.body.position);
|
648 |
if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
|
649 |
currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
|
650 |
|
|
|
659 |
}
|
660 |
});
|
661 |
}
|
|
|
662 |
function reload() {
|
663 |
ammo = 30;
|
664 |
updateAmmoDisplay();
|
|
|
674 |
healthElement.style.width = `${healthPercentage}%`;
|
675 |
}
|
676 |
|
677 |
+
function updateTankHUD() {
|
678 |
+
if (!player || !player.body) return;
|
679 |
+
|
680 |
document.querySelector('#altitude-indicator span').textContent =
|
681 |
+
Math.round(player.body.position.y);
|
682 |
|
683 |
const speed = Math.round(
|
684 |
Math.sqrt(
|
|
|
689 |
document.querySelector('#speed-indicator span').textContent = speed;
|
690 |
|
691 |
const heading = Math.round(
|
692 |
+
(player.body.rotation.y * (180 / Math.PI) + 360) % 360
|
693 |
);
|
694 |
document.querySelector('#compass span').textContent = heading;
|
695 |
|
|
|
697 |
}
|
698 |
|
699 |
function updateRadar() {
|
700 |
+
if (!player || !player.body) return;
|
701 |
+
|
702 |
const radarTargets = document.querySelector('.radar-targets');
|
703 |
radarTargets.innerHTML = '';
|
704 |
|
705 |
enemies.forEach(enemy => {
|
706 |
if (!enemy || !enemy.model) return;
|
707 |
|
708 |
+
const relativePos = enemy.model.position.clone().sub(player.body.position);
|
709 |
const distance = relativePos.length();
|
710 |
|
711 |
if (distance < 500) {
|
712 |
+
const playerAngle = player.body.rotation.y;
|
713 |
const enemyAngle = Math.atan2(relativePos.x, relativePos.z);
|
714 |
const relativeAngle = enemyAngle - playerAngle;
|
715 |
|
|
|
764 |
function gameLoop(timestamp) {
|
765 |
requestAnimationFrame(gameLoop);
|
766 |
|
|
|
767 |
if (timestamp - lastRender < 16) {
|
768 |
return;
|
769 |
}
|
|
|
774 |
updateBullets();
|
775 |
updateEnemies();
|
776 |
updateEnemyBullets();
|
777 |
+
updateTankHUD();
|
778 |
checkGameStatus();
|
779 |
}
|
780 |
|
|
|
823 |
camera,
|
824 |
enemies,
|
825 |
gunSound,
|
826 |
+
player,
|
827 |
reloadEnemies: createEnemies
|
828 |
};
|