gini1 commited on
Commit
2bbf99e
β€’
1 Parent(s): 9301efb

Create game.js

Browse files
Files changed (1) hide show
  1. game.js +444 -0
game.js ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // game.js
2
+ import * as THREE from 'three';
3
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
4
+ import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
5
+
6
+ // κ²Œμž„ μƒμˆ˜
7
+ const GAME_DURATION = 180;
8
+ const MAP_SIZE = 2000;
9
+ const HELICOPTER_HEIGHT = 50;
10
+ const ENEMY_SCALE = 3;
11
+ const MAX_HEALTH = 1000;
12
+ const ENEMY_MODELS = ['1.GLB', '2.GLB', '3.GLB', '4.GLB'];
13
+ const ENEMY_CONFIG = {
14
+ ATTACK_RANGE: 100,
15
+ ATTACK_INTERVAL: 2000,
16
+ BULLET_SPEED: 2
17
+ };
18
+
19
+ // κ²Œμž„ λ³€μˆ˜
20
+ let scene, camera, renderer, controls;
21
+ let enemies = [];
22
+ let bullets = [];
23
+ let enemyBullets = [];
24
+ let playerHealth = MAX_HEALTH;
25
+ let ammo = 30;
26
+ let currentStage = 1;
27
+ let isGameOver = false;
28
+
29
+ // μ‚¬μš΄λ“œ ν’€ 생성 ν•¨μˆ˜
30
+ const createSoundPool = (soundUrl, poolSize = 10) => {
31
+ const sounds = [];
32
+ for (let i = 0; i < poolSize; i++) {
33
+ const sound = new Audio(soundUrl);
34
+ sounds.push(sound);
35
+ }
36
+ let currentIndex = 0;
37
+
38
+ return {
39
+ play: function() {
40
+ sounds[currentIndex].currentTime = 0;
41
+ sounds[currentIndex].play();
42
+ currentIndex = (currentIndex + 1) % poolSize;
43
+ }
44
+ };
45
+ };
46
+
47
+ // μ‚¬μš΄λ“œ μ΄ˆκΈ°ν™”
48
+ const sounds = {
49
+ bgm: new Audio('Music.wav'),
50
+ gunshot: createSoundPool('gun.wav', 10)
51
+ };
52
+ sounds.bgm.loop = true;
53
+
54
+ // 이동 μƒνƒœ
55
+ const moveState = {
56
+ forward: false,
57
+ backward: false,
58
+ left: false,
59
+ right: false
60
+ };
61
+
62
+ function init() {
63
+ // Scene μ΄ˆκΈ°ν™”
64
+ scene = new THREE.Scene();
65
+ scene.background = new THREE.Color(0x87ceeb);
66
+ scene.fog = new THREE.Fog(0x87ceeb, 0, 1500);
67
+
68
+ // Camera μ„€μ •
69
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 3000);
70
+ camera.position.set(0, HELICOPTER_HEIGHT, 0);
71
+
72
+ // Renderer μ„€μ •
73
+ renderer = new THREE.WebGLRenderer({ antialias: true });
74
+ renderer.setSize(window.innerWidth, window.innerHeight);
75
+ renderer.shadowMap.enabled = true;
76
+ document.body.appendChild(renderer.domElement);
77
+
78
+ // μ‘°λͺ… μ„€μ •
79
+ const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
80
+ scene.add(ambientLight);
81
+
82
+ const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
83
+ dirLight.position.set(100, 100, 50);
84
+ dirLight.castShadow = true;
85
+ scene.add(dirLight);
86
+
87
+ // Controls μ„€μ •
88
+ controls = new PointerLockControls(camera, document.body);
89
+
90
+ // 이벀트 λ¦¬μŠ€λ„ˆ μ„€μ •
91
+ document.addEventListener('click', onClick);
92
+ document.addEventListener('keydown', onKeyDown);
93
+ document.addEventListener('keyup', onKeyUp);
94
+ window.addEventListener('resize', onWindowResize);
95
+
96
+ // μ§€ν˜• 생성
97
+ createTerrain();
98
+
99
+ // 적 λ‘œλ“œ
100
+ loadEnemies();
101
+ }
102
+
103
+ function createTerrain() {
104
+ const geometry = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 200, 200);
105
+ const material = new THREE.MeshStandardMaterial({
106
+ color: 0xD2B48C,
107
+ roughness: 0.8,
108
+ metalness: 0.2
109
+ });
110
+
111
+ const vertices = geometry.attributes.position.array;
112
+ for (let i = 0; i < vertices.length; i += 3) {
113
+ vertices[i + 2] = Math.sin(vertices[i] * 0.01) * Math.cos(vertices[i + 1] * 0.01) * 20;
114
+ }
115
+
116
+ geometry.attributes.position.needsUpdate = true;
117
+ geometry.computeVertexNormals();
118
+
119
+ const terrain = new THREE.Mesh(geometry, material);
120
+ terrain.rotation.x = -Math.PI / 2;
121
+ terrain.receiveShadow = true;
122
+ scene.add(terrain);
123
+
124
+ addObstacles();
125
+ }
126
+
127
+ function addObstacles() {
128
+ const rockGeometry = new THREE.DodecahedronGeometry(10);
129
+ const rockMaterial = new THREE.MeshStandardMaterial({
130
+ color: 0x8B4513,
131
+ roughness: 0.9
132
+ });
133
+
134
+ for (let i = 0; i < 100; i++) {
135
+ const rock = new THREE.Mesh(rockGeometry, rockMaterial);
136
+ rock.position.set(
137
+ (Math.random() - 0.5) * MAP_SIZE * 0.9,
138
+ Math.random() * 10,
139
+ (Math.random() - 0.5) * MAP_SIZE * 0.9
140
+ );
141
+ rock.rotation.set(
142
+ Math.random() * Math.PI,
143
+ Math.random() * Math.PI,
144
+ Math.random() * Math.PI
145
+ );
146
+ rock.castShadow = true;
147
+ rock.receiveShadow = true;
148
+ scene.add(rock);
149
+ }
150
+ }
151
+
152
+ function loadEnemies() {
153
+ const loader = new GLTFLoader();
154
+ const enemyCount = 3 + currentStage;
155
+
156
+ for (let i = 0; i < enemyCount; i++) {
157
+ const modelPath = ENEMY_MODELS[i % ENEMY_MODELS.length];
158
+
159
+ loader.load(modelPath, (gltf) => {
160
+ const enemy = gltf.scene;
161
+ enemy.scale.set(ENEMY_SCALE, ENEMY_SCALE, ENEMY_SCALE);
162
+
163
+ const angle = (i / enemyCount) * Math.PI * 2;
164
+ const radius = 200;
165
+ enemy.position.set(
166
+ Math.cos(angle) * radius,
167
+ 10,
168
+ Math.sin(angle) * radius
169
+ );
170
+
171
+ enemy.traverse((node) => {
172
+ if (node.isMesh) {
173
+ node.castShadow = true;
174
+ node.receiveShadow = true;
175
+ node.material.metalness = 0.2;
176
+ node.material.roughness = 0.8;
177
+ }
178
+ });
179
+
180
+ scene.add(enemy);
181
+ enemies.push({
182
+ model: enemy,
183
+ health: 100,
184
+ speed: 0.3 + (currentStage * 0.1),
185
+ lastAttackTime: 0
186
+ });
187
+ });
188
+ }
189
+ }
190
+
191
+ function onClick() {
192
+ if (!controls.isLocked) {
193
+ controls.lock();
194
+ sounds.bgm.play();
195
+ } else if (ammo > 0) {
196
+ shoot();
197
+ }
198
+ }
199
+
200
+ function onKeyDown(event) {
201
+ switch(event.code) {
202
+ case 'KeyW': moveState.forward = true; break;
203
+ case 'KeyS': moveState.backward = true; break;
204
+ case 'KeyA': moveState.left = true; break;
205
+ case 'KeyD': moveState.right = true; break;
206
+ case 'KeyR': reload(); break;
207
+ }
208
+ }
209
+
210
+ function onKeyUp(event) {
211
+ switch(event.code) {
212
+ case 'KeyW': moveState.forward = false; break;
213
+ case 'KeyS': moveState.backward = false; break;
214
+ case 'KeyA': moveState.left = false; break;
215
+ case 'KeyD': moveState.right = false; break;
216
+ }
217
+ }
218
+
219
+ function onWindowResize() {
220
+ camera.aspect = window.innerWidth / window.innerHeight;
221
+ camera.updateProjectionMatrix();
222
+ renderer.setSize(window.innerWidth, window.innerHeight);
223
+ }
224
+
225
+ function shoot() {
226
+ if (ammo <= 0) return;
227
+
228
+ ammo--;
229
+ updateAmmoDisplay();
230
+
231
+ const bullet = createBullet();
232
+ bullets.push(bullet);
233
+
234
+ sounds.gunshot.play();
235
+ }
236
+
237
+ function createBullet() {
238
+ const bulletGeometry = new THREE.SphereGeometry(0.5);
239
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
240
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
241
+
242
+ bullet.position.copy(camera.position);
243
+ const direction = new THREE.Vector3();
244
+ camera.getWorldDirection(direction);
245
+ bullet.velocity = direction.multiplyScalar(3);
246
+
247
+ scene.add(bullet);
248
+ return bullet;
249
+ }
250
+
251
+ function createEnemyBullet(enemy) {
252
+ const bulletGeometry = new THREE.SphereGeometry(0.5);
253
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
254
+ const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
255
+
256
+ bullet.position.copy(enemy.model.position);
257
+
258
+ const direction = new THREE.Vector3();
259
+ direction.subVectors(camera.position, enemy.model.position).normalize();
260
+ bullet.velocity = direction.multiplyScalar(ENEMY_CONFIG.BULLET_SPEED);
261
+
262
+ scene.add(bullet);
263
+ return bullet;
264
+ }
265
+
266
+ function reload() {
267
+ ammo = 30;
268
+ updateAmmoDisplay();
269
+ }
270
+
271
+ function updateAmmoDisplay() {
272
+ document.getElementById('ammo').textContent = `Ammo: ${ammo}/30`;
273
+ }
274
+
275
+ function updateHealthBar() {
276
+ const healthElement = document.getElementById('health');
277
+ const healthPercentage = (playerHealth / MAX_HEALTH) * 100;
278
+ healthElement.style.width = `${healthPercentage}%`;
279
+ }
280
+
281
+ function updateHelicopterHUD() {
282
+ const altitude = Math.round(camera.position.y);
283
+ document.querySelector('#altitude-indicator span').textContent = altitude;
284
+
285
+ const speed = Math.round(
286
+ Math.sqrt(
287
+ moveState.forward * moveState.forward +
288
+ moveState.right * moveState.right
289
+ ) * 100
290
+ );
291
+ document.querySelector('#speed-indicator span').textContent = speed;
292
+
293
+ const heading = Math.round(
294
+ (camera.rotation.y * (180 / Math.PI) + 360) % 360
295
+ );
296
+ document.querySelector('#compass span').textContent = heading;
297
+
298
+ updateRadar();
299
+ }
300
+
301
+ function updateRadar() {
302
+ const radarTargets = document.querySelector('.radar-targets');
303
+ radarTargets.innerHTML = '';
304
+
305
+ enemies.forEach(enemy => {
306
+ const relativePos = enemy.model.position.clone().sub(camera.position);
307
+ const distance = relativePos.length();
308
+
309
+ if (distance < 500) {
310
+ const angle = Math.atan2(relativePos.x, relativePos.z);
311
+ const normalizedDistance = distance / 500;
312
+
313
+ const dot = document.createElement('div');
314
+ dot.className = 'radar-dot';
315
+ dot.style.left = `${50 + Math.sin(angle) * normalizedDistance * 45}%`;
316
+ dot.style.top = `${50 + Math.cos(angle) * normalizedDistance * 45}%`;
317
+ radarTargets.appendChild(dot);
318
+ }
319
+ });
320
+ }
321
+
322
+ function updateMovement() {
323
+ if (controls.isLocked) {
324
+ const speed = 2.0;
325
+ if (moveState.forward) controls.moveForward(speed);
326
+ if (moveState.backward) controls.moveForward(-speed);
327
+ if (moveState.left) controls.moveRight(-speed);
328
+ if (moveState.right) controls.moveRight(speed);
329
+ }
330
+ }
331
+
332
+ function updateBullets() {
333
+ for (let i = bullets.length - 1; i >= 0; i--) {
334
+ bullets[i].position.add(bullets[i].velocity);
335
+
336
+ enemies.forEach(enemy => {
337
+ if (bullets[i].position.distanceTo(enemy.model.position) < 5) {
338
+ scene.remove(bullets[i]);
339
+ bullets.splice(i, 1);
340
+ enemy.health -= 25;
341
+
342
+ if (enemy.health <= 0) {
343
+ scene.remove(enemy.model);
344
+ enemies = enemies.filter(e => e !== enemy);
345
+ }
346
+ }
347
+ });
348
+
349
+ if (bullets[i] && bullets[i].position.distanceTo(camera.position) > 1000) {
350
+ scene.remove(bullets[i]);
351
+ bullets.splice(i, 1);
352
+ }
353
+ }
354
+ }
355
+
356
+ function updateEnemyBullets() {
357
+ for (let i = enemyBullets.length - 1; i >= 0; i--) {
358
+ enemyBullets[i].position.add(enemyBullets[i].velocity);
359
+
360
+ if (enemyBullets[i].position.distanceTo(camera.position) < 3) {
361
+ playerHealth -= 10;
362
+ updateHealthBar();
363
+ scene.remove(enemyBullets[i]);
364
+ enemyBullets.splice(i, 1);
365
+
366
+ if (playerHealth <= 0) {
367
+ gameOver(false);
368
+ }
369
+ continue;
370
+ }
371
+
372
+ if (enemyBullets[i].position.distanceTo(camera.position) > 1000) {
373
+ scene.remove(enemyBullets[i]);
374
+ enemyBullets.splice(i, 1);
375
+ }
376
+ }
377
+ }
378
+
379
+ function updateEnemies() {
380
+ const currentTime = Date.now();
381
+
382
+ enemies.forEach(enemy => {
383
+ const direction = new THREE.Vector3();
384
+ direction.subVectors(camera.position, enemy.model.position);
385
+ direction.normalize();
386
+
387
+ enemy.model.position.add(direction.multiplyScalar(enemy.speed));
388
+ enemy.model.lookAt(camera.position);
389
+
390
+ const distanceToPlayer = enemy.model.position.distanceTo(camera.position);
391
+ if (distanceToPlayer < ENEMY_CONFIG.ATTACK_RANGE &&
392
+ currentTime - enemy.lastAttackTime > ENEMY_CONFIG.ATTACK_INTERVAL) {
393
+ enemyBullets.push(createEnemyBullet(enemy));
394
+ enemy.lastAttackTime = currentTime;
395
+ }
396
+
397
+ if (distanceToPlayer < 10) {
398
+ gameOver(false);
399
+ }
400
+ });
401
+ }
402
+
403
+ function checkGameStatus() {
404
+ if (enemies.length === 0 && currentStage < 5) {
405
+ currentStage++;
406
+ document.getElementById('stage').style.display = 'block';
407
+ document.getElementById('stage').textContent = `Stage ${currentStage}`;
408
+ setTimeout(() => {
409
+ document.getElementById('stage').style.display = 'none';
410
+ loadEnemies();
411
+ }, 2000);
412
+ }
413
+ }
414
+
415
+ function gameOver(won) {
416
+ isGameOver = true;
417
+ controls.unlock();
418
+ sounds.bgm.pause();
419
+ alert(won ? 'Mission Complete!' : 'Game Over!');
420
+ location.reload();
421
+ }
422
+
423
+ let lastTime = performance.now();
424
+ function gameLoop() {
425
+ const time = performance.now();
426
+ const delta = (time - lastTime) / 1000;
427
+ lastTime = time;
428
+
429
+ if (controls.isLocked && !isGameOver) {
430
+ updateMovement();
431
+ updateBullets();
432
+ updateEnemies();
433
+ updateEnemyBullets();
434
+ updateHelicopterHUD();
435
+ checkGameStatus();
436
+ }
437
+
438
+ renderer.render(scene, camera);
439
+ requestAnimationFrame(gameLoop);
440
+ }
441
+
442
+ // κ²Œμž„ μ‹œμž‘
443
+ init();
444
+ gameLoop();