cutechicken commited on
Commit
0fd1d06
β€’
1 Parent(s): f790ee7

Update game.js

Browse files
Files changed (1) hide show
  1. game.js +102 -185
game.js CHANGED
@@ -2,128 +2,72 @@ import * as THREE from 'three';
2
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
3
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
4
 
 
5
  // κ²Œμž„ μƒμˆ˜
6
  const GAME_DURATION = 180;
7
  const MAP_SIZE = 2000;
8
- const TANK_HEIGHT = 0.5; // 포탑 높이 μ‘°μ •
9
  const ENEMY_GROUND_HEIGHT = 0;
10
  const ENEMY_SCALE = 10;
11
  const MAX_HEALTH = 1000;
12
  const ENEMY_MOVE_SPEED = 0.1;
13
  const ENEMY_COUNT_MAX = 5;
14
  const PARTICLE_COUNT = 15;
15
- const OBSTACLE_COUNT = 50;
16
  const ENEMY_CONFIG = {
17
- ATTACK_RANGE: 100,
18
- ATTACK_INTERVAL: 2000,
19
- BULLET_SPEED: 2
20
- };
21
-
22
-
23
- // TankPlayer 클래슀 μˆ˜μ •
24
- class TankPlayer {
25
- constructor() {
26
- this.body = null;
27
- this.turret = null;
28
- this.position = new THREE.Vector3(0, 0, 0);
29
- this.rotation = new THREE.Euler(0, 0, 0);
30
- this.turretRotation = 0;
31
- this.moveSpeed = 0.5;
32
- this.turnSpeed = 0.03;
33
- this.turretGroup = new THREE.Group();
34
- this.health = MAX_HEALTH;
35
- }
36
-
37
- async initialize(scene, loader) {
38
- try {
39
- const bodyResult = await loader.loadAsync('/models/abramsBody.glb');
40
- this.body = bodyResult.scene;
41
- this.body.position.copy(this.position);
42
-
43
- const turretResult = await loader.loadAsync('/models/abramsTurret.glb');
44
- this.turret = turretResult.scene;
45
-
46
- // 포탑 μœ„μΉ˜ μ‘°μ •
47
- this.turretGroup.position.y = 0.2; // 포탑 높이 μ‘°μ •
48
- this.turretGroup.add(this.turret);
49
- this.body.add(this.turretGroup);
50
-
51
- this.body.traverse((child) => {
52
- if (child.isMesh) {
53
- child.castShadow = true;
54
- child.receiveShadow = true;
55
- }
56
- });
57
-
58
- this.turret.traverse((child) => {
59
- if (child.isMesh) {
60
- child.castShadow = true;
61
- child.receiveShadow = true;
62
- }
63
- });
64
-
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.turretGroup) return;
74
-
75
- const targetAngle = Math.atan2(mouseX, mouseY);
76
- const currentRotation = this.turretGroup.rotation.y;
77
- const rotationDiff = targetAngle - currentRotation;
78
-
79
- // 포탑 νšŒμ „ 각도 μ •κ·œν™”
80
- let normalizedDiff = rotationDiff;
81
- while (normalizedDiff > Math.PI) normalizedDiff -= Math.PI * 2;
82
- while (normalizedDiff < -Math.PI) normalizedDiff += Math.PI * 2;
83
-
84
- this.turretGroup.rotation.y += normalizedDiff * 0.1;
85
- }
86
-
87
- move(direction) {
88
- if (!this.body) return;
89
-
90
- const moveVector = new THREE.Vector3();
91
- moveVector.x = direction.x * this.moveSpeed;
92
- moveVector.z = direction.z * this.moveSpeed;
93
-
94
- moveVector.applyEuler(this.body.rotation);
95
- this.body.position.add(moveVector);
96
- }
97
-
98
- rotate(angle) {
99
- if (!this.body) return;
100
- this.body.rotation.y += angle * this.turnSpeed;
101
- }
102
-
103
- getPosition() {
104
- return this.body ? this.body.position : new THREE.Vector3();
105
  }
 
106
 
107
- takeDamage(damage) {
108
- this.health -= damage;
109
- return this.health <= 0;
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
  }
112
 
113
- // Enemy 클래슀 μ •μ˜
114
  class Enemy {
115
- constructor(scene, position) {
116
  this.scene = scene;
117
  this.position = position;
 
118
  this.mesh = null;
119
- this.health = 100;
120
  this.lastAttackTime = 0;
121
  this.bullets = [];
 
122
  }
123
 
124
  async initialize(loader) {
125
  try {
126
- const result = await loader.loadAsync('/models/enemy1.glb');
 
127
  this.mesh = result.scene;
128
  this.mesh.position.copy(this.position);
129
  this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
@@ -144,22 +88,21 @@ class Enemy {
144
  update(playerPosition) {
145
  if (!this.mesh) return;
146
 
147
- // ν”Œλ ˆμ΄μ–΄ λ°©ν–₯으둜 νšŒμ „
148
  const direction = new THREE.Vector3()
149
  .subVectors(playerPosition, this.mesh.position)
150
  .normalize();
151
 
152
  this.mesh.lookAt(playerPosition);
153
 
154
- // ν”Œλ ˆμ΄μ–΄ λ°©ν–₯으둜 이동
155
- this.mesh.position.add(direction.multiplyScalar(ENEMY_MOVE_SPEED));
 
156
 
157
  // μ΄μ•Œ μ—…λ°μ΄νŠΈ
158
  for (let i = this.bullets.length - 1; i >= 0; i--) {
159
  const bullet = this.bullets[i];
160
  bullet.position.add(bullet.velocity);
161
 
162
- // μ΄μ•Œμ΄ 맡 λ°–μœΌλ‘œ λ‚˜κ°€λ©΄ 제거
163
  if (Math.abs(bullet.position.x) > MAP_SIZE ||
164
  Math.abs(bullet.position.z) > MAP_SIZE) {
165
  this.scene.remove(bullet);
@@ -170,10 +113,16 @@ class Enemy {
170
 
171
  shoot(playerPosition) {
172
  const currentTime = Date.now();
173
- if (currentTime - this.lastAttackTime < ENEMY_CONFIG.ATTACK_INTERVAL) return;
 
 
 
 
174
 
175
  const bulletGeometry = new THREE.SphereGeometry(0.2);
176
- const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
 
 
177
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
178
 
179
  bullet.position.copy(this.mesh.position);
@@ -182,7 +131,11 @@ class Enemy {
182
  .subVectors(playerPosition, this.mesh.position)
183
  .normalize();
184
 
185
- bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
 
 
 
 
186
 
187
  this.scene.add(bullet);
188
  this.bullets.push(bullet);
@@ -202,43 +155,9 @@ class Enemy {
202
  }
203
  }
204
  }
205
-
206
- // Particle 클래슀 μ •μ˜
207
- class Particle {
208
- constructor(scene, position) {
209
- const geometry = new THREE.SphereGeometry(0.1);
210
- const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
211
- this.mesh = new THREE.Mesh(geometry, material);
212
- this.mesh.position.copy(position);
213
-
214
- this.velocity = new THREE.Vector3(
215
- (Math.random() - 0.5) * 0.3,
216
- Math.random() * 0.2,
217
- (Math.random() - 0.5) * 0.3
218
- );
219
-
220
- this.gravity = -0.01;
221
- this.lifetime = 60;
222
- this.age = 0;
223
-
224
- scene.add(this.mesh);
225
- }
226
-
227
- update() {
228
- this.velocity.y += this.gravity;
229
- this.mesh.position.add(this.velocity);
230
- this.age++;
231
- return this.age < this.lifetime;
232
- }
233
-
234
- destroy(scene) {
235
- scene.remove(this.mesh);
236
- }
237
- }
238
- // Game 클래슀 μ •μ˜
239
  class Game {
240
  constructor() {
241
- // κΈ°λ³Έ Three.js μ„€μ •
242
  this.scene = new THREE.Scene();
243
  this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
244
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
@@ -246,24 +165,21 @@ class Game {
246
  this.renderer.shadowMap.enabled = true;
247
  document.body.appendChild(this.renderer.domElement);
248
 
249
- // κ²Œμž„ μš”μ†Œ μ΄ˆκΈ°ν™”
250
  this.tank = new TankPlayer();
251
  this.enemies = [];
252
  this.particles = [];
253
- this.obstacles = [];
254
  this.loader = new GLTFLoader();
255
  this.controls = null;
256
  this.gameTime = GAME_DURATION;
257
  this.score = 0;
258
  this.isGameOver = false;
259
 
260
- // 마우슀 μƒνƒœ
261
  this.mouse = {
262
  x: 0,
263
  y: 0
264
  };
265
 
266
- // ν‚€λ³΄λ“œ μƒνƒœ
267
  this.keys = {
268
  forward: false,
269
  backward: false,
@@ -271,10 +187,7 @@ class Game {
271
  right: false
272
  };
273
 
274
- // 이벀트 λ¦¬μŠ€λ„ˆ μ„€μ •
275
  this.setupEventListeners();
276
-
277
- // κ²Œμž„ μ΄ˆκΈ°ν™”
278
  this.initialize();
279
  }
280
 
@@ -288,52 +201,51 @@ class Game {
288
  directionalLight.castShadow = true;
289
  this.scene.add(directionalLight);
290
 
291
- // λ°”λ‹₯ 생성
292
  const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE);
293
  const groundMaterial = new THREE.MeshStandardMaterial({
294
- color: 0x808080,
295
- roughness: 0.8,
296
- metalness: 0.2
297
  });
298
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
299
  ground.rotation.x = -Math.PI / 2;
300
  ground.receiveShadow = true;
301
  this.scene.add(ground);
302
 
 
 
 
303
  // 탱크 μ΄ˆκΈ°ν™”
304
  await this.tank.initialize(this.scene, this.loader);
305
 
306
- // μž₯μ• λ¬Ό 생성
307
- this.createObstacles();
308
-
309
- // 카메라 μœ„μΉ˜ μ„€μ •
310
  this.camera.position.set(0, 10, -10);
311
  this.camera.lookAt(0, 0, 0);
312
 
313
- // 포인터 락 컨트둀 μ„€μ •
314
  this.controls = new PointerLockControls(this.camera, document.body);
315
 
316
- // κ²Œμž„ μ‹œμž‘
317
  this.animate();
318
  this.spawnEnemies();
319
  this.startGameTimer();
320
  }
321
 
322
- createObstacles() {
323
- for (let i = 0; i < OBSTACLE_COUNT; i++) {
324
- const geometry = new THREE.BoxGeometry(2, 2, 2);
325
- const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
326
- const obstacle = new THREE.Mesh(geometry, material);
327
-
328
- obstacle.position.x = (Math.random() - 0.5) * MAP_SIZE;
329
- obstacle.position.z = (Math.random() - 0.5) * MAP_SIZE;
330
- obstacle.position.y = 1;
331
-
332
- obstacle.castShadow = true;
333
- obstacle.receiveShadow = true;
334
-
335
- this.obstacles.push(obstacle);
336
- this.scene.add(obstacle);
 
337
  }
338
  }
339
 
@@ -346,7 +258,9 @@ class Game {
346
  (Math.random() - 0.5) * MAP_SIZE
347
  );
348
 
349
- const enemy = new Enemy(this.scene, position);
 
 
350
  enemy.initialize(this.loader);
351
  this.enemies.push(enemy);
352
  }
@@ -355,7 +269,7 @@ class Game {
355
 
356
  spawnEnemy();
357
  }
358
-
359
  startGameTimer() {
360
  const timer = setInterval(() => {
361
  this.gameTime--;
@@ -367,7 +281,6 @@ class Game {
367
  }
368
 
369
  setupEventListeners() {
370
- // ν‚€λ³΄λ“œ 이벀트
371
  document.addEventListener('keydown', (event) => {
372
  switch(event.code) {
373
  case 'KeyW': this.keys.forward = true; break;
@@ -386,13 +299,11 @@ class Game {
386
  }
387
  });
388
 
389
- // 마우슀 이벀트
390
  document.addEventListener('mousemove', (event) => {
391
  this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
392
  this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
393
  });
394
 
395
- // μ°½ 크기 λ³€κ²½ 이벀트
396
  window.addEventListener('resize', () => {
397
  this.camera.aspect = window.innerWidth / window.innerHeight;
398
  this.camera.updateProjectionMatrix();
@@ -437,22 +348,34 @@ class Game {
437
  this.enemies.forEach(enemy => {
438
  if (!enemy.mesh) return;
439
 
 
440
  enemy.bullets.forEach(bullet => {
441
  const distance = bullet.position.distanceTo(tankPosition);
442
  if (distance < 1) {
443
- if (this.tank.takeDamage(10)) {
444
  this.endGame();
445
  }
446
  this.scene.remove(bullet);
447
  enemy.bullets = enemy.bullets.filter(b => b !== bullet);
448
  }
449
  });
 
 
 
 
 
 
 
 
 
 
 
 
450
  });
451
  }
452
 
453
  endGame() {
454
  this.isGameOver = true;
455
- // κ²Œμž„ μ˜€λ²„ UI ν‘œμ‹œ
456
  const gameOverDiv = document.createElement('div');
457
  gameOverDiv.style.position = 'absolute';
458
  gameOverDiv.style.top = '50%';
@@ -469,27 +392,21 @@ class Game {
469
 
470
  requestAnimationFrame(() => this.animate());
471
 
472
- // 탱크 μ—…λ°μ΄νŠΈ
473
  this.tank.update(this.mouse.x, this.mouse.y);
474
  this.handleMovement();
475
 
476
- // 적 μ—…λ°μ΄νŠΈ
477
  const tankPosition = this.tank.getPosition();
478
  this.enemies.forEach(enemy => {
479
  enemy.update(tankPosition);
480
  const distance = enemy.mesh?.position.distanceTo(tankPosition) || Infinity;
481
- if (distance < ENEMY_CONFIG.ATTACK_RANGE) {
482
  enemy.shoot(tankPosition);
483
  }
484
  });
485
 
486
- // νŒŒν‹°ν΄ μ—…λ°μ΄νŠΈ
487
  this.updateParticles();
488
-
489
- // 좩돌 체크
490
  this.checkCollisions();
491
 
492
- // λ Œλ”λ§
493
  this.renderer.render(this.scene, this.camera);
494
  }
495
  }
 
2
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
3
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
4
 
5
+
6
  // κ²Œμž„ μƒμˆ˜
7
  const GAME_DURATION = 180;
8
  const MAP_SIZE = 2000;
9
+ const TANK_HEIGHT = 0.5;
10
  const ENEMY_GROUND_HEIGHT = 0;
11
  const ENEMY_SCALE = 10;
12
  const MAX_HEALTH = 1000;
13
  const ENEMY_MOVE_SPEED = 0.1;
14
  const ENEMY_COUNT_MAX = 5;
15
  const PARTICLE_COUNT = 15;
16
+ const BUILDING_COUNT = 30; // 건물 수 μΆ”κ°€
17
  const ENEMY_CONFIG = {
18
+ TANK: {
19
+ MODEL: '/models/enemy1.glb',
20
+ HEALTH: 120,
21
+ SPEED: 0.1,
22
+ ATTACK_RANGE: 100,
23
+ ATTACK_INTERVAL: 2000,
24
+ BULLET_SPEED: 2
25
+ },
26
+ HEAVY_TANK: {
27
+ MODEL: '/models/enemy4.glb',
28
+ HEALTH: 200,
29
+ SPEED: 0.05,
30
+ ATTACK_RANGE: 150,
31
+ ATTACK_INTERVAL: 3000,
32
+ BULLET_SPEED: 1.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
+ };
35
 
36
+ // Building 클래슀 μΆ”κ°€
37
+ class Building {
38
+ constructor(scene, position, size) {
39
+ const geometry = new THREE.BoxGeometry(size.width, size.height, size.depth);
40
+ const material = new THREE.MeshStandardMaterial({
41
+ color: 0x808080,
42
+ roughness: 0.7,
43
+ metalness: 0.3
44
+ });
45
+ this.mesh = new THREE.Mesh(geometry, material);
46
+ this.mesh.position.copy(position);
47
+ this.mesh.position.y = size.height / 2;
48
+ this.mesh.castShadow = true;
49
+ this.mesh.receiveShadow = true;
50
+ scene.add(this.mesh);
51
  }
52
  }
53
 
54
+ // Enemy 클래슀 μˆ˜μ •
55
  class Enemy {
56
+ constructor(scene, position, type) {
57
  this.scene = scene;
58
  this.position = position;
59
+ this.type = type;
60
  this.mesh = null;
61
+ this.health = type === 'TANK' ? ENEMY_CONFIG.TANK.HEALTH : ENEMY_CONFIG.HEAVY_TANK.HEALTH;
62
  this.lastAttackTime = 0;
63
  this.bullets = [];
64
+ this.config = ENEMY_CONFIG[type];
65
  }
66
 
67
  async initialize(loader) {
68
  try {
69
+ const modelPath = this.type === 'TANK' ? ENEMY_CONFIG.TANK.MODEL : ENEMY_CONFIG.HEAVY_TANK.MODEL;
70
+ const result = await loader.loadAsync(modelPath);
71
  this.mesh = result.scene;
72
  this.mesh.position.copy(this.position);
73
  this.mesh.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
 
88
  update(playerPosition) {
89
  if (!this.mesh) return;
90
 
 
91
  const direction = new THREE.Vector3()
92
  .subVectors(playerPosition, this.mesh.position)
93
  .normalize();
94
 
95
  this.mesh.lookAt(playerPosition);
96
 
97
+ // νƒ€μž…λ³„ 이동 속도 적용
98
+ const moveSpeed = this.type === 'TANK' ? ENEMY_CONFIG.TANK.SPEED : ENEMY_CONFIG.HEAVY_TANK.SPEED;
99
+ this.mesh.position.add(direction.multiplyScalar(moveSpeed));
100
 
101
  // μ΄μ•Œ μ—…λ°μ΄νŠΈ
102
  for (let i = this.bullets.length - 1; i >= 0; i--) {
103
  const bullet = this.bullets[i];
104
  bullet.position.add(bullet.velocity);
105
 
 
106
  if (Math.abs(bullet.position.x) > MAP_SIZE ||
107
  Math.abs(bullet.position.z) > MAP_SIZE) {
108
  this.scene.remove(bullet);
 
113
 
114
  shoot(playerPosition) {
115
  const currentTime = Date.now();
116
+ const attackInterval = this.type === 'TANK' ?
117
+ ENEMY_CONFIG.TANK.ATTACK_INTERVAL :
118
+ ENEMY_CONFIG.HEAVY_TANK.ATTACK_INTERVAL;
119
+
120
+ if (currentTime - this.lastAttackTime < attackInterval) return;
121
 
122
  const bulletGeometry = new THREE.SphereGeometry(0.2);
123
+ const bulletMaterial = new THREE.MeshBasicMaterial({
124
+ color: this.type === 'TANK' ? 0xff0000 : 0xff6600
125
+ });
126
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
127
 
128
  bullet.position.copy(this.mesh.position);
 
131
  .subVectors(playerPosition, this.mesh.position)
132
  .normalize();
133
 
134
+ const bulletSpeed = this.type === 'TANK' ?
135
+ ENEMY_CONFIG.TANK.BULLET_SPEED :
136
+ ENEMY_CONFIG.HEAVY_TANK.BULLET_SPEED;
137
+
138
+ bullet.velocity = direction.multiplyScalar(bulletSpeed);
139
 
140
  this.scene.add(bullet);
141
  this.bullets.push(bullet);
 
155
  }
156
  }
157
  }
158
+ // Game 클래슀 μˆ˜μ •
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  class Game {
160
  constructor() {
 
161
  this.scene = new THREE.Scene();
162
  this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
163
  this.renderer = new THREE.WebGLRenderer({ antialias: true });
 
165
  this.renderer.shadowMap.enabled = true;
166
  document.body.appendChild(this.renderer.domElement);
167
 
 
168
  this.tank = new TankPlayer();
169
  this.enemies = [];
170
  this.particles = [];
171
+ this.buildings = []; // 건물 λ°°μ—΄ μΆ”κ°€
172
  this.loader = new GLTFLoader();
173
  this.controls = null;
174
  this.gameTime = GAME_DURATION;
175
  this.score = 0;
176
  this.isGameOver = false;
177
 
 
178
  this.mouse = {
179
  x: 0,
180
  y: 0
181
  };
182
 
 
183
  this.keys = {
184
  forward: false,
185
  backward: false,
 
187
  right: false
188
  };
189
 
 
190
  this.setupEventListeners();
 
 
191
  this.initialize();
192
  }
193
 
 
201
  directionalLight.castShadow = true;
202
  this.scene.add(directionalLight);
203
 
204
+ // λ„μ‹œ λ°”λ‹₯ 생성
205
  const groundGeometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE);
206
  const groundMaterial = new THREE.MeshStandardMaterial({
207
+ color: 0x333333, // μ–΄λ‘μš΄ νšŒμƒ‰ (μ•„μŠ€νŒ”νŠΈ)
208
+ roughness: 0.9,
209
+ metalness: 0.1
210
  });
211
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
212
  ground.rotation.x = -Math.PI / 2;
213
  ground.receiveShadow = true;
214
  this.scene.add(ground);
215
 
216
+ // 건물 생성
217
+ this.createBuildings();
218
+
219
  // 탱크 μ΄ˆκΈ°ν™”
220
  await this.tank.initialize(this.scene, this.loader);
221
 
222
+ // 카메라 μ„€μ •
 
 
 
223
  this.camera.position.set(0, 10, -10);
224
  this.camera.lookAt(0, 0, 0);
225
 
 
226
  this.controls = new PointerLockControls(this.camera, document.body);
227
 
 
228
  this.animate();
229
  this.spawnEnemies();
230
  this.startGameTimer();
231
  }
232
 
233
+ createBuildings() {
234
+ for (let i = 0; i < BUILDING_COUNT; i++) {
235
+ const size = {
236
+ width: 10 + Math.random() * 20,
237
+ height: 20 + Math.random() * 80,
238
+ depth: 10 + Math.random() * 20
239
+ };
240
+
241
+ const position = new THREE.Vector3(
242
+ (Math.random() - 0.5) * (MAP_SIZE - size.width),
243
+ 0,
244
+ (Math.random() - 0.5) * (MAP_SIZE - size.depth)
245
+ );
246
+
247
+ const building = new Building(this.scene, position, size);
248
+ this.buildings.push(building);
249
  }
250
  }
251
 
 
258
  (Math.random() - 0.5) * MAP_SIZE
259
  );
260
 
261
+ // λžœλ€ν•˜κ²Œ 적 μœ ν˜• 선택
262
+ const type = Math.random() > 0.7 ? 'HEAVY_TANK' : 'TANK';
263
+ const enemy = new Enemy(this.scene, position, type);
264
  enemy.initialize(this.loader);
265
  this.enemies.push(enemy);
266
  }
 
269
 
270
  spawnEnemy();
271
  }
272
+ // Game 클래슀 계속...
273
  startGameTimer() {
274
  const timer = setInterval(() => {
275
  this.gameTime--;
 
281
  }
282
 
283
  setupEventListeners() {
 
284
  document.addEventListener('keydown', (event) => {
285
  switch(event.code) {
286
  case 'KeyW': this.keys.forward = true; break;
 
299
  }
300
  });
301
 
 
302
  document.addEventListener('mousemove', (event) => {
303
  this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
304
  this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
305
  });
306
 
 
307
  window.addEventListener('resize', () => {
308
  this.camera.aspect = window.innerWidth / window.innerHeight;
309
  this.camera.updateProjectionMatrix();
 
348
  this.enemies.forEach(enemy => {
349
  if (!enemy.mesh) return;
350
 
351
+ // μ΄μ•Œ 좩돌 체크
352
  enemy.bullets.forEach(bullet => {
353
  const distance = bullet.position.distanceTo(tankPosition);
354
  if (distance < 1) {
355
+ if (this.tank.takeDamage(enemy.type === 'HEAVY_TANK' ? 15 : 10)) {
356
  this.endGame();
357
  }
358
  this.scene.remove(bullet);
359
  enemy.bullets = enemy.bullets.filter(b => b !== bullet);
360
  }
361
  });
362
+
363
+ // 건물과의 좩돌 체크
364
+ this.buildings.forEach(building => {
365
+ // κ°„λ‹¨ν•œ 좩돌 체크 (μ‹€μ œ κ²Œμž„μ—μ„œλŠ” 더 μ •κ΅ν•œ 좩돌 체크가 ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€)
366
+ const distance = enemy.mesh.position.distanceTo(building.mesh.position);
367
+ if (distance < 5) { // μž„μ˜μ˜ 좩돌 거리
368
+ const pushDirection = new THREE.Vector3()
369
+ .subVectors(enemy.mesh.position, building.mesh.position)
370
+ .normalize();
371
+ enemy.mesh.position.add(pushDirection);
372
+ }
373
+ });
374
  });
375
  }
376
 
377
  endGame() {
378
  this.isGameOver = true;
 
379
  const gameOverDiv = document.createElement('div');
380
  gameOverDiv.style.position = 'absolute';
381
  gameOverDiv.style.top = '50%';
 
392
 
393
  requestAnimationFrame(() => this.animate());
394
 
 
395
  this.tank.update(this.mouse.x, this.mouse.y);
396
  this.handleMovement();
397
 
 
398
  const tankPosition = this.tank.getPosition();
399
  this.enemies.forEach(enemy => {
400
  enemy.update(tankPosition);
401
  const distance = enemy.mesh?.position.distanceTo(tankPosition) || Infinity;
402
+ if (distance < enemy.config.ATTACK_RANGE) {
403
  enemy.shoot(tankPosition);
404
  }
405
  });
406
 
 
407
  this.updateParticles();
 
 
408
  this.checkCollisions();
409
 
 
410
  this.renderer.render(this.scene, this.camera);
411
  }
412
  }