Aleksmorshen commited on
Commit
aa4bb9d
·
verified ·
1 Parent(s): e0245e2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1368 -560
index.html CHANGED
@@ -1,11 +1,10 @@
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>Doom-Style 3D Shooter</title>
7
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.min.js"></script>
9
  <style>
10
  * {
11
  margin: 0;
@@ -13,6 +12,9 @@
13
  box-sizing: border-box;
14
  font-family: 'Courier New', monospace;
15
  touch-action: none;
 
 
 
16
  }
17
 
18
  body {
@@ -20,13 +22,14 @@
20
  background: #000;
21
  color: #ff5555;
22
  height: 100vh;
23
- perspective: 1px;
24
  }
25
 
26
  #gameContainer {
27
  position: relative;
28
  width: 100%;
29
  height: 100vh;
 
30
  overflow: hidden;
31
  }
32
 
@@ -54,84 +57,164 @@
54
  top: 50%;
55
  left: 50%;
56
  transform: translate(-50%, -50%);
57
- width: 30px;
58
- height: 30px;
59
- border: 2px solid #ff5555;
60
  border-radius: 50%;
61
- pointer-events: none;
62
  }
63
 
64
  #crosshair::before, #crosshair::after {
65
  content: '';
66
  position: absolute;
67
  background: #ff5555;
 
68
  }
69
 
70
  #crosshair::before {
71
  width: 2px;
72
- height: 10px;
73
- top: -12px;
74
  left: 50%;
75
  transform: translateX(-50%);
76
  }
77
 
78
  #crosshair::after {
79
- width: 10px;
 
 
 
 
 
 
 
 
 
80
  height: 2px;
 
81
  top: 50%;
82
- right: -12px;
83
  transform: translateY(-50%);
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
 
86
  #healthBar {
87
  position: absolute;
88
  bottom: 30px;
89
  left: 30px;
90
- width: 200px;
91
- height: 30px;
92
- background: rgba(0, 0, 0, 0.7);
93
- border: 2px solid #ff5555;
94
- border-radius: 5px;
95
  overflow: hidden;
 
96
  }
97
 
98
  #healthFill {
99
  height: 100%;
100
  width: 100%;
101
- background: linear-gradient(90deg, #ff0000, #ff5555);
102
- transition: width 0.3s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
  #ammoCounter {
106
  position: absolute;
107
  bottom: 30px;
108
  right: 30px;
109
- font-size: 24px;
110
- color: #ff5555;
111
- text-shadow: 0 0 5px #ff0000;
112
- background: rgba(0, 0, 0, 0.7);
113
- padding: 10px 20px;
114
- border: 2px solid #ff5555;
115
  border-radius: 5px;
 
 
 
 
 
 
 
116
  }
117
 
118
  #score {
119
  position: absolute;
120
  top: 30px;
121
  left: 30px;
122
- font-size: 24px;
123
  color: #ff5555;
 
 
 
 
 
 
 
 
 
124
  text-shadow: 0 0 5px #ff0000;
125
  }
126
 
 
 
 
 
 
 
 
 
 
 
127
  #startScreen {
128
  position: absolute;
129
  top: 0;
130
  left: 0;
131
  width: 100%;
132
  height: 100%;
133
- background: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 0, 0, 0.9)),
134
- url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23110000"/><path d="M0 0L100 100M100 0L0 100" stroke="%23300" stroke-width="1"/></svg>');
135
  display: flex;
136
  flex-direction: column;
137
  justify-content: center;
@@ -141,38 +224,45 @@
141
  }
142
 
143
  #title {
144
- font-size: 72px;
145
- margin-bottom: 30px;
146
- text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
147
- letter-spacing: 5px;
 
 
 
 
 
 
148
  }
149
 
150
  #subtitle {
151
  font-size: 24px;
152
- margin-bottom: 50px;
153
  color: #ff9999;
 
154
  }
155
 
156
  #startButton {
157
- background: #ff0000;
158
  color: #fff;
159
  border: none;
160
- padding: 15px 50px;
161
- font-size: 24px;
162
  cursor: pointer;
163
  border-radius: 5px;
164
  text-transform: uppercase;
165
- letter-spacing: 3px;
166
  font-weight: bold;
167
- box-shadow: 0 0 15px #ff0000;
168
  transition: all 0.3s;
169
  pointer-events: auto;
170
  }
171
 
172
  #startButton:hover {
173
- background: #ff5555;
174
- transform: scale(1.05);
175
- box-shadow: 0 0 25px #ff0000;
176
  }
177
 
178
  #gameOverScreen {
@@ -181,7 +271,7 @@
181
  left: 0;
182
  width: 100%;
183
  height: 100%;
184
- background: rgba(0, 0, 0, 0.85);
185
  display: none;
186
  flex-direction: column;
187
  justify-content: center;
@@ -190,129 +280,174 @@
190
  }
191
 
192
  #gameOverTitle {
193
- font-size: 60px;
194
- margin-bottom: 20px;
195
  color: #ff0000;
196
- text-shadow: 0 0 10px #ff0000;
 
 
 
 
 
 
197
  }
198
 
199
  #finalScore {
200
- font-size: 36px;
 
 
 
 
 
 
201
  margin-bottom: 40px;
202
  color: #ff9999;
203
  }
204
 
205
  #restartButton {
206
- background: #ff0000;
207
  color: #fff;
208
  border: none;
209
- padding: 15px 50px;
210
- font-size: 24px;
211
  cursor: pointer;
212
  border-radius: 5px;
213
  text-transform: uppercase;
214
- letter-spacing: 3px;
215
  font-weight: bold;
216
- box-shadow: 0 0 15px #ff0000;
217
  transition: all 0.3s;
 
218
  }
219
 
220
  #restartButton:hover {
221
- background: #ff5555;
222
- transform: scale(1.05);
223
- box-shadow: 0 0 25px #ff0000;
224
- }
225
-
226
- #weapon {
227
- position: absolute;
228
- bottom: 0;
229
- left: 50%;
230
- transform: translateX(-50%);
231
- width: 300px;
232
- height: 200px;
233
- background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200"><rect x="50" y="100" width="200" height="30" fill="%23333"/><rect x="100" y="50" width="100" height="50" fill="%23444"/><rect x="130" y="30" width="40" height="20" fill="%23555"/><circle cx="150" cy="115" r="15" fill="%23222"/></svg>') no-repeat center bottom;
234
- background-size: contain;
235
- z-index: 3;
236
- transition: transform 0.1s;
237
  }
238
 
239
- .hitEffect {
240
  position: absolute;
241
  top: 0;
242
  left: 0;
243
  width: 100%;
244
  height: 100%;
245
- background: rgba(255, 0, 0, 0.3);
246
  pointer-events: none;
247
  opacity: 0;
248
  z-index: 4;
 
249
  }
250
 
251
- #bloodEffect {
252
  position: absolute;
253
  top: 0;
254
  left: 0;
255
  width: 100%;
256
  height: 100%;
257
- background: radial-gradient(circle, rgba(255,0,0,0.7) 0%, rgba(255,0,0,0) 70%);
258
  pointer-events: none;
259
  opacity: 0;
260
- z-index: 5;
 
261
  }
262
 
263
- /* Mobile Controls */
264
  #mobileControls {
265
  position: absolute;
266
- bottom: 20px;
267
  width: 100%;
 
268
  display: none;
269
  justify-content: space-between;
270
- padding: 0 20px;
271
  z-index: 10;
272
  pointer-events: none;
273
  }
274
 
275
- .controlPad {
276
- width: 120px;
277
- height: 120px;
278
- background: rgba(255, 0, 0, 0.3);
279
- border-radius: 50%;
280
- display: flex;
281
- justify-content: center;
282
- align-items: center;
283
  pointer-events: auto;
284
  }
285
 
286
- .controlStick {
287
- width: 50px;
288
- height: 50px;
289
- background: rgba(255, 85, 85, 0.7);
 
290
  border-radius: 50%;
291
- position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  }
293
 
294
  #shootButton {
295
- width: 80px;
296
- height: 80px;
297
- background: rgba(255, 0, 0, 0.5);
 
298
  border-radius: 50%;
299
  display: flex;
300
  justify-content: center;
301
  align-items: center;
302
- font-size: 24px;
 
303
  color: white;
 
304
  pointer-events: auto;
305
- border: 2px solid #ff5555;
 
 
 
306
  }
307
 
308
- #instructions {
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  position: absolute;
310
  bottom: 160px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  width: 100%;
312
- text-align: center;
313
- color: #ff9999;
314
- font-size: 18px;
315
- padding: 0 20px;
316
  }
317
 
318
  @media (max-width: 768px) {
@@ -320,17 +455,60 @@
320
  display: flex;
321
  }
322
 
323
- #instructions {
324
- display: block;
325
- }
326
-
327
  #title {
328
  font-size: 48px;
329
  }
330
 
331
  #subtitle {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  font-size: 18px;
333
  }
 
 
 
 
334
  }
335
  </style>
336
  </head>
@@ -339,525 +517,1082 @@
339
  <div id="gameCanvas"></div>
340
 
341
  <div id="ui">
342
- <div id="crosshair"></div>
 
 
 
 
343
  <div id="healthBar">
344
  <div id="healthFill"></div>
345
  </div>
346
- <div id="ammoCounter">AMMO: 30</div>
347
- <div id="score">SCORE: 0</div>
348
- <div id="weapon"></div>
349
- <div class="hitEffect" id="hitEffect"></div>
350
- <div id="bloodEffect"></div>
 
 
 
 
 
 
 
 
 
 
351
  </div>
352
 
353
  <div id="mobileControls">
354
- <div class="controlPad" id="movementPad">
355
- <div class="controlStick" id="movementStick"></div>
 
 
 
 
 
356
  </div>
357
  <div id="shootButton">FIRE</div>
 
358
  </div>
359
 
360
- <div id="instructions">Use joystick to move, tap FIRE to shoot</div>
361
-
362
  <div id="startScreen">
363
  <h1 id="title">DEMON SLAYER</h1>
364
- <p id="subtitle">Eliminate all demons to survive</p>
365
  <button id="startButton">START MISSION</button>
366
  </div>
367
 
368
  <div id="gameOverScreen">
369
  <h1 id="gameOverTitle">MISSION FAILED</h1>
370
- <p id="finalScore">SCORE: 0</p>
 
371
  <button id="restartButton">TRY AGAIN</button>
372
  </div>
373
  </div>
374
 
375
- <script>
376
- // Game variables
377
- let scene, camera, renderer, controls;
378
- let player, enemies = [], bullets = [];
379
- let playerHealth = 100;
380
- let ammo = 30;
381
- let score = 0;
382
- let gameActive = false;
383
- let moveForward = false;
384
- let moveBackward = false;
385
- let moveLeft = false;
386
- let moveRight = false;
387
- let canShoot = true;
388
- let lastShotTime = 0;
389
- let shotCooldown = 300; // ms
390
- let enemySpawnTimer = 0;
 
 
 
 
 
 
 
 
 
391
  let clock = new THREE.Clock();
392
  let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
- // Mobile controls
395
- let movementStickActive = false;
396
- let movementStickPosition = { x: 0, y: 0 };
397
- let movementStickRadius = 35;
398
-
399
- // Initialize the game
400
  function init() {
401
- // Create scene
402
  scene = new THREE.Scene();
403
- scene.background = new THREE.Color(0x110000);
404
- scene.fog = new THREE.Fog(0x110000, 10, 50);
405
 
406
- // Create camera
407
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
408
- camera.position.y = 1.6;
409
 
410
- // Create renderer
411
- renderer = new THREE.WebGLRenderer({ antialias: true });
 
 
412
  renderer.setSize(window.innerWidth, window.innerHeight);
 
413
  renderer.shadowMap.enabled = true;
 
 
 
414
  document.getElementById('gameCanvas').appendChild(renderer.domElement);
415
 
416
- // Add lighting
417
- const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
418
- scene.add(ambientLight);
 
 
 
 
419
 
420
- const directionalLight = new THREE.DirectionalLight(0xff5555, 0.8);
421
- directionalLight.position.set(10, 20, 10);
422
- directionalLight.castShadow = true;
423
- scene.add(directionalLight);
424
 
425
- // Create floor
426
- const floorGeometry = new THREE.PlaneGeometry(100, 100);
427
- const floorMaterial = new THREE.MeshStandardMaterial({
428
- color: 0x222222,
429
- roughness: 0.8,
430
- metalness: 0.2
431
- });
432
- const floor = new THREE.Mesh(floorGeometry, floorMaterial);
433
- floor.rotation.x = -Math.PI / 2;
434
- floor.receiveShadow = true;
435
- scene.add(floor);
436
 
437
- // Create walls
438
- createWalls();
439
 
440
- // Create pillars
441
- createPillars();
 
 
 
442
 
443
- // Create player
444
- player = new THREE.Object3D();
445
- player.position.set(0, 1.6, 0);
446
- scene.add(player);
447
- player.add(camera);
448
 
449
- // Add pointer lock controls for desktop
450
- if (!isMobile) {
451
- controls = new THREE.PointerLockControls(camera, document.body);
452
- }
 
453
 
454
- // Event listeners
455
- if (!isMobile) {
456
- document.addEventListener('keydown', onKeyDown, false);
457
- document.addEventListener('keyup', onKeyUp, false);
458
- document.addEventListener('mousedown', onMouseDown, false);
459
- document.addEventListener('mousemove', onMouseMove, false);
460
- } else {
461
- setupMobileControls();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  }
463
 
464
- window.addEventListener('resize', onWindowResize, false);
 
 
 
465
 
466
- // Start button event
467
- document.getElementById('startButton').addEventListener('click', startGame);
468
- document.getElementById('restartButton').addEventListener('click', restartGame);
 
469
 
470
- // Start animation loop
471
- animate();
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  }
473
 
474
- function createWalls() {
475
- const wallMaterial = new THREE.MeshStandardMaterial({
476
- color: 0x330000,
 
477
  roughness: 0.9,
478
- metalness: 0.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  });
480
 
481
- // Create a simple maze-like structure
482
  const walls = [
483
- // Outer walls
484
- { x: 0, y: 2, z: -25, width: 50, height: 4, depth: 1 },
485
- { x: 0, y: 2, z: 25, width: 50, height: 4, depth: 1 },
486
- { x: -25, y: 2, z: 0, width: 1, height: 4, depth: 50 },
487
- { x: 25, y: 2, z: 0, width: 1, height: 4, depth: 50 },
488
-
489
- // Inner walls
490
- { x: -10, y: 2, z: -10, width: 1, height: 4, depth: 20 },
491
- { x: 10, y: 2, z: 10, width: 1, height: 4, depth: 20 },
492
- { x: 0, y: 2, z: 0, width: 20, height: 4, depth: 1 },
493
- { x: 0, y: 2, z: -20, width: 20, height: 4, depth: 1 }
494
  ];
495
 
496
- walls.forEach(wall => {
497
- const wallGeometry = new THREE.BoxGeometry(wall.width, wall.height, wall.depth);
498
- const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
499
- wallMesh.position.set(wall.x, wall.y, wall.z);
500
- wallMesh.castShadow = true;
501
- wallMesh.receiveShadow = true;
502
- scene.add(wallMesh);
 
 
 
 
 
 
 
 
503
  });
 
 
 
504
  }
505
 
506
  function createPillars() {
507
- const pillarMaterial = new THREE.MeshStandardMaterial({
508
- color: 0x440000,
 
509
  roughness: 0.7,
510
  metalness: 0.3
511
  });
512
 
513
- const pillarGeometry = new THREE.CylinderGeometry(1, 1, 4, 16);
514
-
515
  const positions = [
516
- { x: -20, z: -20 },
517
- { x: 20, z: -20 },
518
- { x: -20, z: 20 },
519
- { x: 20, z: 20 },
520
- { x: 0, z: -15 },
521
- { x: 0, z: 15 },
522
- { x: -15, z: 0 },
523
- { x: 15, z: 0 }
524
  ];
525
 
526
  positions.forEach(pos => {
527
  const pillar = new THREE.Mesh(pillarGeometry, pillarMaterial);
528
- pillar.position.set(pos.x, 2, pos.z);
529
  pillar.castShadow = true;
530
  pillar.receiveShadow = true;
531
  scene.add(pillar);
 
 
 
 
 
 
 
 
 
 
532
  });
533
  }
534
 
535
- // Create a detailed demon model
536
- function createDemon() {
537
- const demonGroup = new THREE.Group();
538
-
539
- // Body
540
- const bodyGeometry = new THREE.SphereGeometry(0.8, 16, 16);
541
- const bodyMaterial = new THREE.MeshStandardMaterial({
542
- color: 0x880000,
543
- roughness: 0.7,
544
- metalness: 0.3
545
  });
546
- const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
547
- body.position.y = 1;
548
- body.castShadow = true;
549
- demonGroup.add(body);
550
 
551
- // Head
552
- const headGeometry = new THREE.SphereGeometry(0.6, 16, 16);
553
- const headMaterial = new THREE.MeshStandardMaterial({
554
- color: 0xaa0000,
555
- roughness: 0.7,
556
- metalness: 0.3
557
- });
558
- const head = new THREE.Mesh(headGeometry, headMaterial);
559
- head.position.y = 1.8;
560
- head.castShadow = true;
561
- demonGroup.add(head);
562
 
563
- // Horns
564
- const hornGeometry = new THREE.ConeGeometry(0.1, 0.8, 8);
565
- const hornMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
 
566
 
567
- const horn1 = new THREE.Mesh(hornGeometry, hornMaterial);
568
- horn1.position.set(-0.2, 2.3, 0);
569
- horn1.rotation.z = Math.PI / 6;
570
- horn1.castShadow = true;
571
- demonGroup.add(horn1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
 
573
- const horn2 = new THREE.Mesh(hornGeometry, hornMaterial);
574
- horn2.position.set(0.2, 2.3, 0);
575
- horn2.rotation.z = -Math.PI / 6;
576
- horn2.castShadow = true;
577
- demonGroup.add(horn2);
 
 
 
 
 
 
 
 
578
 
579
- // Eyes
580
- const eyeGeometry = new THREE.SphereGeometry(0.15, 8, 8);
581
- const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 });
 
 
 
 
 
582
 
583
- const eye1 = new THREE.Mesh(eyeGeometry, eyeMaterial);
584
- eye1.position.set(-0.25, 1.9, 0.4);
585
- demonGroup.add(eye1);
 
 
586
 
587
- const eye2 = new THREE.Mesh(eyeGeometry, eyeMaterial);
588
- eye2.position.set(0.25, 1.9, 0.4);
589
- demonGroup.add(eye2);
590
 
591
- // Arms
592
- const armGeometry = new THREE.CylinderGeometry(0.15, 0.15, 1.2, 8);
593
- const armMaterial = new THREE.MeshStandardMaterial({ color: 0x770000 });
 
 
 
 
594
 
595
- const arm1 = new THREE.Mesh(armGeometry, armMaterial);
596
- arm1.position.set(-0.9, 1, 0);
597
- arm1.rotation.z = Math.PI / 4;
598
- arm1.castShadow = true;
599
- demonGroup.add(arm1);
 
 
 
 
 
 
 
 
 
600
 
601
- const arm2 = new THREE.Mesh(armGeometry, armMaterial);
602
- arm2.position.set(0.9, 1, 0);
603
- arm2.rotation.z = -Math.PI / 4;
604
- arm2.castShadow = true;
605
- demonGroup.add(arm2);
606
 
607
- // Legs
608
- const legGeometry = new THREE.CylinderGeometry(0.2, 0.2, 1, 8);
609
- const legMaterial = new THREE.MeshStandardMaterial({ color: 0x660000 });
610
 
611
- const leg1 = new THREE.Mesh(legGeometry, legMaterial);
612
- leg1.position.set(-0.4, 0.3, 0);
613
- leg1.castShadow = true;
614
- demonGroup.add(leg1);
615
 
616
- const leg2 = new THREE.Mesh(legGeometry, legMaterial);
617
- leg2.position.set(0.4, 0.3, 0);
618
- leg2.castShadow = true;
619
- demonGroup.add(leg2);
620
 
621
- return demonGroup;
622
- }
623
-
624
- function spawnEnemy() {
625
- const demon = createDemon();
626
- demon.position.set(
627
- (Math.random() - 0.5) * 40,
628
- 0,
629
- (Math.random() - 0.5) * 40
630
- );
631
- demon.rotation.y = Math.random() * Math.PI * 2;
632
- scene.add(demon);
633
 
634
- enemies.push({
635
- mesh: demon,
636
- health: 100,
637
- speed: 0.02 + Math.random() * 0.03,
638
- lastAttack: 0,
639
- attackCooldown: 1000 + Math.random() * 2000
 
 
 
 
640
  });
641
  }
642
 
643
- function startGame() {
644
- document.getElementById('startScreen').style.display = 'none';
645
  if (!isMobile) {
646
- controls.lock();
647
- }
648
- gameActive = true;
649
-
650
- // Spawn initial enemies
651
- for (let i = 0; i < 5; i++) {
652
- spawnEnemy();
 
 
 
 
 
 
 
 
653
  }
654
  }
655
 
656
- function restartGame() {
657
- document.getElementById('gameOverScreen').style.display = 'none';
658
- playerHealth = 100;
659
- ammo = 30;
660
- score = 0;
661
- updateUI();
 
662
 
663
- // Remove all enemies
664
- enemies.forEach(enemy => {
665
- scene.remove(enemy.mesh);
 
 
 
 
 
 
666
  });
667
- enemies = [];
668
 
669
- // Remove all bullets
670
- bullets.forEach(bullet => {
671
- scene.remove(bullet.mesh);
 
 
 
 
672
  });
673
- bullets = [];
674
 
675
- // Reset player position
676
- player.position.set(0, 1.6, 0);
 
 
 
 
 
 
 
 
 
 
677
 
678
- // Spawn initial enemies
679
- for (let i = 0; i < 5; i++) {
680
- spawnEnemy();
681
- }
 
 
 
 
682
 
683
- if (!isMobile) {
684
- controls.lock();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  }
686
- gameActive = true;
687
  }
688
 
689
- function gameOver() {
690
- gameActive = false;
691
- if (!isMobile) {
692
- controls.unlock();
 
 
 
 
693
  }
694
- document.getElementById('finalScore').textContent = `SCORE: ${score}`;
695
- document.getElementById('gameOverScreen').style.display = 'flex';
696
- }
697
-
698
- function updateUI() {
699
- document.getElementById('healthFill').style.width = `${playerHealth}%`;
700
- document.getElementById('ammoCounter').textContent = `AMMO: ${ammo}`;
701
- document.getElementById('score').textContent = `SCORE: ${score}`;
702
  }
703
 
704
- function onKeyDown(event) {
705
- switch (event.code) {
706
- case 'KeyW': moveForward = true; break;
707
- case 'KeyS': moveBackward = true; break;
708
- case 'KeyA': moveLeft = true; break;
709
- case 'KeyD': moveRight = true; break;
710
  }
711
  }
712
 
713
- function onKeyUp(event) {
714
- switch (event.code) {
715
- case 'KeyW': moveForward = false; break;
716
- case 'KeyS': moveBackward = false; break;
717
- case 'KeyA': moveLeft = false; break;
718
- case 'KeyD': moveRight = false; break;
719
- }
720
  }
721
 
722
- function onMouseDown() {
723
- if (!gameActive) return;
724
-
725
- if (ammo > 0 && canShoot) {
726
- shoot();
727
- canShoot = false;
728
- setTimeout(() => { canShoot = true; }, shotCooldown);
729
- }
730
- }
731
 
732
- function onMouseMove(event) {
733
- if (!gameActive || isMobile) return;
734
-
735
- // Add weapon sway effect
736
- const weapon = document.getElementById('weapon');
737
- const moveX = (event.movementX || 0) * 0.05;
738
- const moveY = (event.movementY || 0) * 0.05;
739
- weapon.style.transform = `translateX(calc(-50% + ${moveX}px)) translateY(${moveY}px)`;
740
  }
741
 
742
  function shoot() {
 
 
743
  ammo--;
744
  updateUI();
 
 
745
 
746
- // Create bullet
747
- const bulletGeometry = new THREE.SphereGeometry(0.1, 8, 8);
748
- const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
 
 
 
 
 
749
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
750
 
751
- // Position bullet at camera
752
- bullet.position.set(0, 0, 0);
753
- camera.getWorldPosition(bullet.position);
754
 
755
- // Direction vector
756
  const direction = new THREE.Vector3(0, 0, -1);
757
  direction.applyQuaternion(camera.quaternion);
758
 
 
 
 
 
 
759
  scene.add(bullet);
 
 
 
 
760
  bullets.push({
761
  mesh: bullet,
762
  direction: direction,
763
- speed: 1.0,
764
  distance: 0
765
  });
766
 
767
- // Weapon recoil effect
768
- const weapon = document.getElementById('weapon');
769
- weapon.style.transform = 'translateX(-50%) translateY(20px)';
770
- setTimeout(() => {
771
- weapon.style.transform = 'translateX(-50%) translateY(0)';
772
- }, 100);
773
-
774
- // Muzzle flash effect
775
- const flash = document.createElement('div');
776
- flash.style.position = 'absolute';
777
- flash.style.width = '50px';
778
- flash.style.height = '50px';
779
- flash.style.background = 'radial-gradient(circle, #ffff00, #ff5500, transparent)';
780
- flash.style.borderRadius = '50%';
781
- flash.style.top = '50%';
782
- flash.style.left = '50%';
783
- flash.style.transform = 'translate(-50%, -50%)';
784
- flash.style.pointerEvents = 'none';
785
- flash.style.zIndex = '10';
786
- document.getElementById('ui').appendChild(flash);
787
 
788
- setTimeout(() => {
789
- document.getElementById('ui').removeChild(flash);
790
- }, 50);
791
  }
792
 
793
- function setupMobileControls() {
794
- const movementPad = document.getElementById('movementPad');
795
- const movementStick = document.getElementById('movementStick');
796
- const shootButton = document.getElementById('shootButton');
797
-
798
- // Movement pad touch events
799
- movementPad.addEventListener('touchstart', (e) => {
800
- movementStickActive = true;
801
- updateMovementStick(e.touches[0]);
802
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
803
 
804
- movementPad.addEventListener('touchmove', (e) => {
805
- if (movementStickActive) {
806
- e.preventDefault();
807
- updateMovementStick(e.touches[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
809
  });
 
 
 
 
810
 
811
- movementPad.addEventListener('touchend', () => {
812
- movementStickActive = false;
813
- movementStickPosition = { x: 0, y: 0 };
814
- movementStick.style.transform = 'translate(0, 0)';
815
- moveForward = false;
816
- moveBackward = false;
817
- moveLeft = false;
818
- moveRight = false;
819
  });
 
 
 
 
820
 
821
- // Shoot button touch events
822
- shootButton.addEventListener('touchstart', (e) => {
823
- e.preventDefault();
824
- if (gameActive && ammo > 0 && canShoot) {
825
- shoot();
826
- canShoot = false;
827
- setTimeout(() => { canShoot = true; }, shotCooldown);
828
- }
829
  });
830
 
831
- function updateMovementStick(touch) {
832
- const padRect = movementPad.getBoundingClientRect();
833
- const centerX = padRect.left + padRect.width / 2;
834
- const centerY = padRect.top + padRect.height / 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
 
836
- let deltaX = touch.clientX - centerX;
837
- let deltaY = touch.clientY - centerY;
838
 
839
- // Limit to pad radius
840
- const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
841
- if (distance > movementStickRadius) {
842
- deltaX = deltaX * movementStickRadius / distance;
843
- deltaY = deltaY * movementStickRadius / distance;
844
- }
845
 
846
- movementStickPosition = { x: deltaX, y: deltaY };
847
- movementStick.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
 
849
- // Update movement flags
850
- moveForward = deltaY < -10;
851
- moveBackward = deltaY > 10;
852
- moveLeft = deltaX < -10;
853
- moveRight = deltaX > 10;
 
 
 
 
 
 
 
 
 
854
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  }
856
 
857
- function onWindowResize() {
858
- camera.aspect = window.innerWidth / window.innerHeight;
859
- camera.updateProjectionMatrix();
860
- renderer.setSize(window.innerWidth, window.innerHeight);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
861
  }
862
 
863
  function animate() {
@@ -866,129 +1601,202 @@
866
  const delta = clock.getDelta();
867
 
868
  if (gameActive) {
869
- // Player movement
870
- const speed = 0.1;
871
-
872
- if (isMobile) {
873
- // Mobile movement based on joystick
874
- if (moveForward) player.translateZ(-speed);
875
- if (moveBackward) player.translateZ(speed);
876
- if (moveLeft) player.translateX(-speed);
877
- if (moveRight) player.translateX(speed);
878
- } else {
879
- // Desktop movement
880
- if (moveForward) player.translateZ(-speed);
881
- if (moveBackward) player.translateZ(speed);
882
- if (moveLeft) player.translateX(-speed);
883
- if (moveRight) player.translateX(speed);
884
  }
885
 
886
- // Keep player within bounds
887
- player.position.x = Math.max(-24, Math.min(24, player.position.x));
888
- player.position.z = Math.max(-24, Math.min(24, player.position.z));
 
 
 
 
889
 
890
- // Enemy AI
891
- enemies.forEach((enemy, index) => {
892
- // Move towards player
893
- const direction = new THREE.Vector3();
894
- direction.subVectors(player.position, enemy.mesh.position).normalize();
895
- enemy.mesh.position.add(direction.multiplyScalar(enemy.speed));
 
 
 
 
 
896
 
897
- // Rotate to face player
898
- enemy.mesh.lookAt(player.position);
899
- enemy.mesh.rotation.x = 0; // Keep upright
900
- enemy.mesh.rotation.z = 0;
901
 
902
- // Attack player if close
903
- const distance = player.position.distanceTo(enemy.mesh.position);
904
- if (distance < 3 && Date.now() - enemy.lastAttack > enemy.attackCooldown) {
905
- playerHealth -= 10;
906
- updateUI();
907
- enemy.lastAttack = Date.now();
908
-
909
- // Show hit effect
910
- const hitEffect = document.getElementById('hitEffect');
911
- hitEffect.style.opacity = '1';
912
- setTimeout(() => {
913
- hitEffect.style.opacity = '0';
914
- }, 100);
915
-
916
- // Show blood effect
917
- const bloodEffect = document.getElementById('bloodEffect');
918
- bloodEffect.style.opacity = '0.7';
919
- setTimeout(() => {
920
- bloodEffect.style.opacity = '0';
921
- }, 200);
922
-
923
- if (playerHealth <= 0) {
924
- gameOver();
925
- }
926
  }
927
 
928
- // Remove dead enemies
929
- if (enemy.health <= 0) {
930
- scene.remove(enemy.mesh);
931
- enemies.splice(index, 1);
932
- score += 100;
933
- updateUI();
934
-
935
- // Spawn new enemy
936
- if (Math.random() > 0.7) {
937
- spawnEnemy();
938
- }
939
- }
940
- });
941
 
942
- // Update bullets
943
  for (let i = bullets.length - 1; i >= 0; i--) {
944
  const bullet = bullets[i];
945
  bullet.mesh.position.add(bullet.direction.clone().multiplyScalar(bullet.speed));
946
  bullet.distance += bullet.speed;
947
 
948
- // Remove bullets that travel too far
949
- if (bullet.distance > 50) {
950
  scene.remove(bullet.mesh);
951
  bullets.splice(i, 1);
952
  continue;
953
  }
954
 
955
- // Check for collisions with enemies
956
  for (let j = enemies.length - 1; j >= 0; j--) {
957
  const enemy = enemies[j];
958
- const distance = bullet.mesh.position.distanceTo(enemy.mesh.position);
959
 
960
- if (distance < 1.5) {
961
- // Hit enemy
962
- enemy.health -= 25;
 
963
  scene.remove(bullet.mesh);
964
  bullets.splice(i, 1);
965
 
966
- // Enemy hit effect
967
- enemy.mesh.children[0].material.color.set(0xff0000);
968
- setTimeout(() => {
969
- if (enemy.mesh && enemy.mesh.children[0]) {
970
- enemy.mesh.children[0].material.color.set(0x880000);
971
- }
972
- }, 100);
973
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
974
  break;
975
  }
976
  }
977
  }
978
 
979
- // Spawn enemies periodically
980
- enemySpawnTimer += delta;
981
- if (enemySpawnTimer > 5) {
982
- spawnEnemy();
983
- enemySpawnTimer = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
  }
986
 
987
- renderer.render(scene, camera);
 
 
 
 
 
 
 
988
  }
989
 
990
- // Initialize the game when the page loads
991
- window.onload = init;
992
  </script>
993
  </body>
994
- </html>
 
1
+
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
+ <title>DEMON SLAYER - AAA Edition</title>
 
 
8
  <style>
9
  * {
10
  margin: 0;
 
12
  box-sizing: border-box;
13
  font-family: 'Courier New', monospace;
14
  touch-action: none;
15
+ -webkit-touch-callout: none;
16
+ -webkit-user-select: none;
17
+ user-select: none;
18
  }
19
 
20
  body {
 
22
  background: #000;
23
  color: #ff5555;
24
  height: 100vh;
25
+ height: 100dvh;
26
  }
27
 
28
  #gameContainer {
29
  position: relative;
30
  width: 100%;
31
  height: 100vh;
32
+ height: 100dvh;
33
  overflow: hidden;
34
  }
35
 
 
57
  top: 50%;
58
  left: 50%;
59
  transform: translate(-50%, -50%);
60
+ width: 4px;
61
+ height: 4px;
62
+ background: #ff0000;
63
  border-radius: 50%;
64
+ box-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
65
  }
66
 
67
  #crosshair::before, #crosshair::after {
68
  content: '';
69
  position: absolute;
70
  background: #ff5555;
71
+ box-shadow: 0 0 5px #ff0000;
72
  }
73
 
74
  #crosshair::before {
75
  width: 2px;
76
+ height: 15px;
77
+ top: -20px;
78
  left: 50%;
79
  transform: translateX(-50%);
80
  }
81
 
82
  #crosshair::after {
83
+ width: 15px;
84
+ height: 2px;
85
+ top: 50%;
86
+ left: 15px;
87
+ transform: translateY(-50%);
88
+ }
89
+
90
+ .crosshair-left {
91
+ position: absolute;
92
+ width: 15px;
93
  height: 2px;
94
+ background: #ff5555;
95
  top: 50%;
96
+ right: 15px;
97
  transform: translateY(-50%);
98
+ box-shadow: 0 0 5px #ff0000;
99
+ }
100
+
101
+ .crosshair-bottom {
102
+ position: absolute;
103
+ width: 2px;
104
+ height: 15px;
105
+ background: #ff5555;
106
+ bottom: -20px;
107
+ left: 50%;
108
+ transform: translateX(-50%);
109
+ box-shadow: 0 0 5px #ff0000;
110
  }
111
 
112
  #healthBar {
113
  position: absolute;
114
  bottom: 30px;
115
  left: 30px;
116
+ width: 250px;
117
+ height: 25px;
118
+ background: rgba(0, 0, 0, 0.8);
119
+ border: 2px solid #ff3333;
120
+ border-radius: 3px;
121
  overflow: hidden;
122
+ box-shadow: 0 0 15px rgba(255, 0, 0, 0.5), inset 0 0 10px rgba(0, 0, 0, 0.5);
123
  }
124
 
125
  #healthFill {
126
  height: 100%;
127
  width: 100%;
128
+ background: linear-gradient(180deg, #ff6666 0%, #cc0000 50%, #990000 100%);
129
+ transition: width 0.3s ease-out;
130
+ box-shadow: inset 0 2px 5px rgba(255, 255, 255, 0.3);
131
+ }
132
+
133
+ #healthText {
134
+ position: absolute;
135
+ bottom: 60px;
136
+ left: 30px;
137
+ font-size: 14px;
138
+ color: #ff9999;
139
+ text-shadow: 0 0 5px #ff0000;
140
+ letter-spacing: 2px;
141
+ }
142
+
143
+ #armorBar {
144
+ position: absolute;
145
+ bottom: 70px;
146
+ left: 30px;
147
+ width: 200px;
148
+ height: 15px;
149
+ background: rgba(0, 0, 0, 0.8);
150
+ border: 2px solid #3366ff;
151
+ border-radius: 3px;
152
+ overflow: hidden;
153
+ box-shadow: 0 0 15px rgba(0, 100, 255, 0.5);
154
+ }
155
+
156
+ #armorFill {
157
+ height: 100%;
158
+ width: 50%;
159
+ background: linear-gradient(180deg, #6699ff 0%, #0033cc 50%, #002299 100%);
160
+ transition: width 0.3s ease-out;
161
  }
162
 
163
  #ammoCounter {
164
  position: absolute;
165
  bottom: 30px;
166
  right: 30px;
167
+ font-size: 36px;
168
+ color: #ffcc00;
169
+ text-shadow: 0 0 10px #ff6600, 0 0 20px #ff3300;
170
+ background: rgba(0, 0, 0, 0.8);
171
+ padding: 15px 25px;
172
+ border: 2px solid #ff6600;
173
  border-radius: 5px;
174
+ box-shadow: 0 0 20px rgba(255, 100, 0, 0.5);
175
+ }
176
+
177
+ #ammoLabel {
178
+ font-size: 12px;
179
+ color: #ff9966;
180
+ letter-spacing: 2px;
181
  }
182
 
183
  #score {
184
  position: absolute;
185
  top: 30px;
186
  left: 30px;
187
+ font-size: 28px;
188
  color: #ff5555;
189
+ text-shadow: 0 0 10px #ff0000;
190
+ }
191
+
192
+ #killCount {
193
+ position: absolute;
194
+ top: 70px;
195
+ left: 30px;
196
+ font-size: 18px;
197
+ color: #ff9999;
198
  text-shadow: 0 0 5px #ff0000;
199
  }
200
 
201
+ #waveInfo {
202
+ position: absolute;
203
+ top: 30px;
204
+ right: 30px;
205
+ font-size: 24px;
206
+ color: #ff6666;
207
+ text-shadow: 0 0 10px #ff0000;
208
+ text-align: right;
209
+ }
210
+
211
  #startScreen {
212
  position: absolute;
213
  top: 0;
214
  left: 0;
215
  width: 100%;
216
  height: 100%;
217
+ background: radial-gradient(ellipse at center, rgba(30, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.95) 100%);
 
218
  display: flex;
219
  flex-direction: column;
220
  justify-content: center;
 
224
  }
225
 
226
  #title {
227
+ font-size: 80px;
228
+ margin-bottom: 20px;
229
+ text-shadow: 0 0 20px #ff0000, 0 0 40px #ff0000, 0 0 60px #ff0000;
230
+ letter-spacing: 10px;
231
+ animation: pulse 2s infinite;
232
+ }
233
+
234
+ @keyframes pulse {
235
+ 0%, 100% { text-shadow: 0 0 20px #ff0000, 0 0 40px #ff0000; }
236
+ 50% { text-shadow: 0 0 30px #ff0000, 0 0 60px #ff0000, 0 0 80px #ff0000; }
237
  }
238
 
239
  #subtitle {
240
  font-size: 24px;
241
+ margin-bottom: 60px;
242
  color: #ff9999;
243
+ letter-spacing: 5px;
244
  }
245
 
246
  #startButton {
247
+ background: linear-gradient(180deg, #ff3333 0%, #cc0000 100%);
248
  color: #fff;
249
  border: none;
250
+ padding: 20px 60px;
251
+ font-size: 28px;
252
  cursor: pointer;
253
  border-radius: 5px;
254
  text-transform: uppercase;
255
+ letter-spacing: 5px;
256
  font-weight: bold;
257
+ box-shadow: 0 0 30px #ff0000, inset 0 2px 10px rgba(255, 255, 255, 0.3);
258
  transition: all 0.3s;
259
  pointer-events: auto;
260
  }
261
 
262
  #startButton:hover {
263
+ background: linear-gradient(180deg, #ff5555 0%, #ee2222 100%);
264
+ transform: scale(1.1);
265
+ box-shadow: 0 0 50px #ff0000;
266
  }
267
 
268
  #gameOverScreen {
 
271
  left: 0;
272
  width: 100%;
273
  height: 100%;
274
+ background: rgba(0, 0, 0, 0.9);
275
  display: none;
276
  flex-direction: column;
277
  justify-content: center;
 
280
  }
281
 
282
  #gameOverTitle {
283
+ font-size: 70px;
284
+ margin-bottom: 30px;
285
  color: #ff0000;
286
+ text-shadow: 0 0 20px #ff0000;
287
+ animation: flicker 0.5s infinite;
288
+ }
289
+
290
+ @keyframes flicker {
291
+ 0%, 100% { opacity: 1; }
292
+ 50% { opacity: 0.8; }
293
  }
294
 
295
  #finalScore {
296
+ font-size: 40px;
297
+ margin-bottom: 20px;
298
+ color: #ffcc00;
299
+ }
300
+
301
+ #finalKills {
302
+ font-size: 24px;
303
  margin-bottom: 40px;
304
  color: #ff9999;
305
  }
306
 
307
  #restartButton {
308
+ background: linear-gradient(180deg, #ff3333 0%, #cc0000 100%);
309
  color: #fff;
310
  border: none;
311
+ padding: 20px 60px;
312
+ font-size: 28px;
313
  cursor: pointer;
314
  border-radius: 5px;
315
  text-transform: uppercase;
316
+ letter-spacing: 5px;
317
  font-weight: bold;
318
+ box-shadow: 0 0 30px #ff0000;
319
  transition: all 0.3s;
320
+ pointer-events: auto;
321
  }
322
 
323
  #restartButton:hover {
324
+ background: linear-gradient(180deg, #ff5555 0%, #ee2222 100%);
325
+ transform: scale(1.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  }
327
 
328
+ #hitEffect {
329
  position: absolute;
330
  top: 0;
331
  left: 0;
332
  width: 100%;
333
  height: 100%;
334
+ background: radial-gradient(ellipse at center, transparent 0%, rgba(255, 0, 0, 0.4) 100%);
335
  pointer-events: none;
336
  opacity: 0;
337
  z-index: 4;
338
+ transition: opacity 0.1s;
339
  }
340
 
341
+ #damageVignette {
342
  position: absolute;
343
  top: 0;
344
  left: 0;
345
  width: 100%;
346
  height: 100%;
347
+ background: radial-gradient(ellipse at center, transparent 40%, rgba(100, 0, 0, 0.8) 100%);
348
  pointer-events: none;
349
  opacity: 0;
350
+ z-index: 3;
351
+ transition: opacity 0.5s;
352
  }
353
 
 
354
  #mobileControls {
355
  position: absolute;
356
+ bottom: 0;
357
  width: 100%;
358
+ height: 200px;
359
  display: none;
360
  justify-content: space-between;
361
+ padding: 20px 30px;
362
  z-index: 10;
363
  pointer-events: none;
364
  }
365
 
366
+ .joystickContainer {
367
+ width: 150px;
368
+ height: 150px;
369
+ position: relative;
 
 
 
 
370
  pointer-events: auto;
371
  }
372
 
373
+ .joystickBase {
374
+ width: 150px;
375
+ height: 150px;
376
+ background: radial-gradient(circle, rgba(255, 50, 50, 0.3) 0%, rgba(255, 0, 0, 0.1) 100%);
377
+ border: 3px solid rgba(255, 50, 50, 0.5);
378
  border-radius: 50%;
379
+ position: absolute;
380
+ top: 0;
381
+ left: 0;
382
+ }
383
+
384
+ .joystickStick {
385
+ width: 60px;
386
+ height: 60px;
387
+ background: radial-gradient(circle, rgba(255, 100, 100, 0.9) 0%, rgba(200, 50, 50, 0.8) 100%);
388
+ border: 2px solid rgba(255, 150, 150, 0.8);
389
+ border-radius: 50%;
390
+ position: absolute;
391
+ top: 50%;
392
+ left: 50%;
393
+ transform: translate(-50%, -50%);
394
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.5);
395
  }
396
 
397
  #shootButton {
398
+ width: 100px;
399
+ height: 100px;
400
+ background: radial-gradient(circle, rgba(255, 100, 0, 0.8) 0%, rgba(200, 50, 0, 0.6) 100%);
401
+ border: 3px solid rgba(255, 150, 50, 0.8);
402
  border-radius: 50%;
403
  display: flex;
404
  justify-content: center;
405
  align-items: center;
406
+ font-size: 16px;
407
+ font-weight: bold;
408
  color: white;
409
+ text-shadow: 0 0 10px #ff6600;
410
  pointer-events: auto;
411
+ box-shadow: 0 0 30px rgba(255, 100, 0, 0.5);
412
+ position: absolute;
413
+ bottom: 50px;
414
+ right: 180px;
415
  }
416
 
417
+ #reloadButton {
418
+ width: 70px;
419
+ height: 70px;
420
+ background: radial-gradient(circle, rgba(100, 100, 255, 0.8) 0%, rgba(50, 50, 200, 0.6) 100%);
421
+ border: 3px solid rgba(150, 150, 255, 0.8);
422
+ border-radius: 50%;
423
+ display: flex;
424
+ justify-content: center;
425
+ align-items: center;
426
+ font-size: 12px;
427
+ font-weight: bold;
428
+ color: white;
429
+ pointer-events: auto;
430
+ box-shadow: 0 0 20px rgba(100, 100, 255, 0.5);
431
  position: absolute;
432
  bottom: 160px;
433
+ right: 200px;
434
+ }
435
+
436
+ #minimap {
437
+ position: absolute;
438
+ top: 100px;
439
+ right: 30px;
440
+ width: 150px;
441
+ height: 150px;
442
+ background: rgba(0, 0, 0, 0.7);
443
+ border: 2px solid #ff5555;
444
+ border-radius: 5px;
445
+ overflow: hidden;
446
+ }
447
+
448
+ #minimapCanvas {
449
  width: 100%;
450
+ height: 100%;
 
 
 
451
  }
452
 
453
  @media (max-width: 768px) {
 
455
  display: flex;
456
  }
457
 
 
 
 
 
458
  #title {
459
  font-size: 48px;
460
  }
461
 
462
  #subtitle {
463
+ font-size: 16px;
464
+ padding: 0 20px;
465
+ }
466
+
467
+ #startButton, #restartButton {
468
+ padding: 15px 40px;
469
+ font-size: 20px;
470
+ }
471
+
472
+ #healthBar {
473
+ width: 150px;
474
+ height: 20px;
475
+ bottom: 220px;
476
+ left: 20px;
477
+ }
478
+
479
+ #armorBar {
480
+ width: 120px;
481
+ height: 12px;
482
+ bottom: 250px;
483
+ left: 20px;
484
+ }
485
+
486
+ #ammoCounter {
487
+ bottom: 220px;
488
+ right: 20px;
489
+ font-size: 24px;
490
+ padding: 10px 15px;
491
+ }
492
+
493
+ #score {
494
+ top: 20px;
495
+ left: 20px;
496
+ font-size: 20px;
497
+ }
498
+
499
+ #killCount {
500
+ top: 50px;
501
+ font-size: 14px;
502
+ }
503
+
504
+ #waveInfo {
505
+ top: 20px;
506
  font-size: 18px;
507
  }
508
+
509
+ #minimap {
510
+ display: none;
511
+ }
512
  }
513
  </style>
514
  </head>
 
517
  <div id="gameCanvas"></div>
518
 
519
  <div id="ui">
520
+ <div id="crosshair">
521
+ <div class="crosshair-left"></div>
522
+ <div class="crosshair-bottom"></div>
523
+ </div>
524
+ <div id="healthText">HEALTH</div>
525
  <div id="healthBar">
526
  <div id="healthFill"></div>
527
  </div>
528
+ <div id="armorBar">
529
+ <div id="armorFill"></div>
530
+ </div>
531
+ <div id="ammoCounter">
532
+ <div id="ammoLabel">AMMO</div>
533
+ <span id="ammoValue">30</span> / <span id="ammoMax">30</span>
534
+ </div>
535
+ <div id="score">SCORE: <span id="scoreValue">0</span></div>
536
+ <div id="killCount">KILLS: <span id="killValue">0</span></div>
537
+ <div id="waveInfo">WAVE: <span id="waveValue">1</span></div>
538
+ <div id="hitEffect"></div>
539
+ <div id="damageVignette"></div>
540
+ <div id="minimap">
541
+ <canvas id="minimapCanvas"></canvas>
542
+ </div>
543
  </div>
544
 
545
  <div id="mobileControls">
546
+ <div class="joystickContainer" id="moveJoystick">
547
+ <div class="joystickBase"></div>
548
+ <div class="joystickStick" id="moveStick"></div>
549
+ </div>
550
+ <div class="joystickContainer" id="lookJoystick">
551
+ <div class="joystickBase"></div>
552
+ <div class="joystickStick" id="lookStick"></div>
553
  </div>
554
  <div id="shootButton">FIRE</div>
555
+ <div id="reloadButton">R</div>
556
  </div>
557
 
 
 
558
  <div id="startScreen">
559
  <h1 id="title">DEMON SLAYER</h1>
560
+ <p id="subtitle">SURVIVE THE ENDLESS HORDE</p>
561
  <button id="startButton">START MISSION</button>
562
  </div>
563
 
564
  <div id="gameOverScreen">
565
  <h1 id="gameOverTitle">MISSION FAILED</h1>
566
+ <p id="finalScore">SCORE: <span id="finalScoreValue">0</span></p>
567
+ <p id="finalKills">DEMONS SLAIN: <span id="finalKillValue">0</span></p>
568
  <button id="restartButton">TRY AGAIN</button>
569
  </div>
570
  </div>
571
 
572
+ <script type="importmap">
573
+ {
574
+ "imports": {
575
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
576
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
577
+ }
578
+ }
579
+ </script>
580
+
581
+ <script type="module">
582
+ import * as THREE from 'three';
583
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
584
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
585
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
586
+ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
587
+ import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
588
+ import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
589
+
590
+ let scene, camera, renderer, composer;
591
+ let player, enemies = [], bullets = [], particles = [], decals = [];
592
+ let playerHealth = 100, playerArmor = 50, ammo = 30, maxAmmo = 30, reserveAmmo = 120;
593
+ let score = 0, kills = 0, wave = 1;
594
+ let gameActive = false, isReloading = false;
595
+ let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
596
+ let canShoot = true, lastShotTime = 0, shotCooldown = 100;
597
  let clock = new THREE.Clock();
598
  let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
599
+ let screenShake = { intensity: 0, decay: 0.9 };
600
+ let playerVelocity = new THREE.Vector3();
601
+ let cameraShakeOffset = new THREE.Vector3();
602
+ let weaponBob = 0, weaponRecoil = 0;
603
+ let moveJoystickData = { active: false, x: 0, y: 0 };
604
+ let lookJoystickData = { active: false, x: 0, y: 0, lastX: 0, lastY: 0 };
605
+ let mouseMovement = { x: 0, y: 0 };
606
+ let isPointerLocked = false;
607
+ let floorTexture, wallTexture, ceilingTexture;
608
+ let pointLights = [];
609
+ let ambientParticles = [];
610
+ let minimapCtx;
611
+ let collisionObjects = [];
612
 
 
 
 
 
 
 
613
  function init() {
 
614
  scene = new THREE.Scene();
615
+ scene.background = new THREE.Color(0x050505);
616
+ scene.fog = new THREE.FogExp2(0x0a0505, 0.02);
617
 
618
+ camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.1, 1000);
619
+ camera.position.set(0, 1.7, 0);
 
620
 
621
+ renderer = new THREE.WebGLRenderer({
622
+ antialias: true,
623
+ powerPreference: "high-performance"
624
+ });
625
  renderer.setSize(window.innerWidth, window.innerHeight);
626
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
627
  renderer.shadowMap.enabled = true;
628
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
629
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
630
+ renderer.toneMappingExposure = 0.8;
631
  document.getElementById('gameCanvas').appendChild(renderer.domElement);
632
 
633
+ setupPostProcessing();
634
+ createTextures();
635
+ createLevel();
636
+ createLighting();
637
+ createAmbientParticles();
638
+ setupControls();
639
+ setupMinimap();
640
 
641
+ player = new THREE.Object3D();
642
+ player.position.set(0, 1.7, 0);
643
+ player.add(camera);
644
+ scene.add(player);
645
 
646
+ document.getElementById('startButton').addEventListener('click', startGame);
647
+ document.getElementById('startButton').addEventListener('touchend', startGame);
648
+ document.getElementById('restartButton').addEventListener('click', restartGame);
649
+ document.getElementById('restartButton').addEventListener('touchend', restartGame);
 
 
 
 
 
 
 
650
 
651
+ window.addEventListener('resize', onWindowResize);
 
652
 
653
+ animate();
654
+ }
655
+
656
+ function setupPostProcessing() {
657
+ composer = new EffectComposer(renderer);
658
 
659
+ const renderPass = new RenderPass(scene, camera);
660
+ composer.addPass(renderPass);
 
 
 
661
 
662
+ const bloomPass = new UnrealBloomPass(
663
+ new THREE.Vector2(window.innerWidth, window.innerHeight),
664
+ 0.5, 0.4, 0.85
665
+ );
666
+ composer.addPass(bloomPass);
667
 
668
+ const smaaPass = new SMAAPass(window.innerWidth, window.innerHeight);
669
+ composer.addPass(smaaPass);
670
+
671
+ const vignetteShader = {
672
+ uniforms: {
673
+ tDiffuse: { value: null },
674
+ darkness: { value: 1.0 },
675
+ offset: { value: 1.0 }
676
+ },
677
+ vertexShader: `
678
+ varying vec2 vUv;
679
+ void main() {
680
+ vUv = uv;
681
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
682
+ }
683
+ `,
684
+ fragmentShader: `
685
+ uniform sampler2D tDiffuse;
686
+ uniform float darkness;
687
+ uniform float offset;
688
+ varying vec2 vUv;
689
+ void main() {
690
+ vec4 texel = texture2D(tDiffuse, vUv);
691
+ vec2 uv = (vUv - vec2(0.5)) * vec2(offset);
692
+ float vignette = 1.0 - dot(uv, uv);
693
+ texel.rgb *= mix(1.0 - darkness, 1.0, vignette);
694
+ gl_FragColor = texel;
695
+ }
696
+ `
697
+ };
698
+
699
+ const vignettePass = new ShaderPass(vignetteShader);
700
+ vignettePass.uniforms.darkness.value = 0.6;
701
+ vignettePass.uniforms.offset.value = 1.2;
702
+ composer.addPass(vignettePass);
703
+ }
704
+
705
+ function createTextures() {
706
+ const canvas = document.createElement('canvas');
707
+ canvas.width = 512;
708
+ canvas.height = 512;
709
+ const ctx = canvas.getContext('2d');
710
+
711
+ ctx.fillStyle = '#1a1a1a';
712
+ ctx.fillRect(0, 0, 512, 512);
713
+
714
+ for (let i = 0; i < 512; i += 64) {
715
+ for (let j = 0; j < 512; j += 64) {
716
+ ctx.fillStyle = `rgb(${20 + Math.random() * 10}, ${15 + Math.random() * 10}, ${15 + Math.random() * 10})`;
717
+ ctx.fillRect(i, j, 62, 62);
718
+
719
+ for (let k = 0; k < 20; k++) {
720
+ ctx.fillStyle = `rgba(0, 0, 0, ${Math.random() * 0.3})`;
721
+ ctx.fillRect(i + Math.random() * 60, j + Math.random() * 60, 2, 2);
722
+ }
723
+ }
724
  }
725
 
726
+ floorTexture = new THREE.CanvasTexture(canvas);
727
+ floorTexture.wrapS = THREE.RepeatWrapping;
728
+ floorTexture.wrapT = THREE.RepeatWrapping;
729
+ floorTexture.repeat.set(20, 20);
730
 
731
+ const wallCanvas = document.createElement('canvas');
732
+ wallCanvas.width = 256;
733
+ wallCanvas.height = 256;
734
+ const wallCtx = wallCanvas.getContext('2d');
735
 
736
+ const gradient = wallCtx.createLinearGradient(0, 0, 0, 256);
737
+ gradient.addColorStop(0, '#2a1515');
738
+ gradient.addColorStop(0.5, '#1a0a0a');
739
+ gradient.addColorStop(1, '#0a0505');
740
+ wallCtx.fillStyle = gradient;
741
+ wallCtx.fillRect(0, 0, 256, 256);
742
+
743
+ for (let i = 0; i < 50; i++) {
744
+ wallCtx.fillStyle = `rgba(${Math.random() * 50}, 0, 0, ${Math.random() * 0.2})`;
745
+ wallCtx.fillRect(Math.random() * 256, Math.random() * 256, Math.random() * 20, Math.random() * 20);
746
+ }
747
+
748
+ wallTexture = new THREE.CanvasTexture(wallCanvas);
749
+ wallTexture.wrapS = THREE.RepeatWrapping;
750
+ wallTexture.wrapT = THREE.RepeatWrapping;
751
  }
752
 
753
+ function createLevel() {
754
+ const floorGeometry = new THREE.PlaneGeometry(100, 100, 50, 50);
755
+ const floorMaterial = new THREE.MeshStandardMaterial({
756
+ map: floorTexture,
757
  roughness: 0.9,
758
+ metalness: 0.1,
759
+ bumpScale: 0.02
760
+ });
761
+
762
+ const positions = floorGeometry.attributes.position;
763
+ for (let i = 0; i < positions.count; i++) {
764
+ positions.setZ(i, Math.random() * 0.05);
765
+ }
766
+ floorGeometry.computeVertexNormals();
767
+
768
+ const floor = new THREE.Mesh(floorGeometry, floorMaterial);
769
+ floor.rotation.x = -Math.PI / 2;
770
+ floor.receiveShadow = true;
771
+ scene.add(floor);
772
+
773
+ const ceilingGeometry = new THREE.PlaneGeometry(100, 100);
774
+ const ceilingMaterial = new THREE.MeshStandardMaterial({
775
+ color: 0x0a0505,
776
+ roughness: 1,
777
+ metalness: 0
778
+ });
779
+ const ceiling = new THREE.Mesh(ceilingGeometry, ceilingMaterial);
780
+ ceiling.rotation.x = Math.PI / 2;
781
+ ceiling.position.y = 5;
782
+ scene.add(ceiling);
783
+
784
+ const wallMaterial = new THREE.MeshStandardMaterial({
785
+ map: wallTexture,
786
+ roughness: 0.85,
787
+ metalness: 0.15,
788
+ side: THREE.DoubleSide
789
  });
790
 
 
791
  const walls = [
792
+ { x: 0, z: -50, rx: 0, ry: 0, w: 100, h: 5 },
793
+ { x: 0, z: 50, rx: 0, ry: Math.PI, w: 100, h: 5 },
794
+ { x: -50, z: 0, rx: 0, ry: Math.PI / 2, w: 100, h: 5 },
795
+ { x: 50, z: 0, rx: 0, ry: -Math.PI / 2, w: 100, h: 5 },
796
+ { x: -20, z: -20, rx: 0, ry: 0, w: 30, h: 5 },
797
+ { x: 20, z: 20, rx: 0, ry: Math.PI, w: 30, h: 5 },
798
+ { x: 0, z: 0, rx: 0, ry: Math.PI / 2, w: 20, h: 5 },
799
+ { x: -30, z: 10, rx: 0, ry: 0, w: 15, h: 5 },
800
+ { x: 30, z: -10, rx: 0, ry: Math.PI, w: 15, h: 5 }
 
 
801
  ];
802
 
803
+ walls.forEach(w => {
804
+ const wallGeometry = new THREE.PlaneGeometry(w.w, w.h);
805
+ const wall = new THREE.Mesh(wallGeometry, wallMaterial);
806
+ wall.position.set(w.x, 2.5, w.z);
807
+ wall.rotation.y = w.ry;
808
+ wall.castShadow = true;
809
+ wall.receiveShadow = true;
810
+ scene.add(wall);
811
+
812
+ collisionObjects.push({
813
+ position: new THREE.Vector3(w.x, 2.5, w.z),
814
+ rotation: w.ry,
815
+ width: w.w,
816
+ depth: 0.5
817
+ });
818
  });
819
+
820
+ createPillars();
821
+ createDecorations();
822
  }
823
 
824
  function createPillars() {
825
+ const pillarGeometry = new THREE.CylinderGeometry(0.8, 1, 5, 12);
826
+ const pillarMaterial = new THREE.MeshStandardMaterial({
827
+ color: 0x2a1010,
828
  roughness: 0.7,
829
  metalness: 0.3
830
  });
831
 
 
 
832
  const positions = [
833
+ [-15, -15], [15, -15], [-15, 15], [15, 15],
834
+ [-30, -30], [30, -30], [-30, 30], [30, 30],
835
+ [0, -30], [0, 30], [-30, 0], [30, 0],
836
+ [-8, 8], [8, -8]
 
 
 
 
837
  ];
838
 
839
  positions.forEach(pos => {
840
  const pillar = new THREE.Mesh(pillarGeometry, pillarMaterial);
841
+ pillar.position.set(pos[0], 2.5, pos[1]);
842
  pillar.castShadow = true;
843
  pillar.receiveShadow = true;
844
  scene.add(pillar);
845
+
846
+ const topGeometry = new THREE.CylinderGeometry(1.2, 0.8, 0.5, 12);
847
+ const top = new THREE.Mesh(topGeometry, pillarMaterial);
848
+ top.position.set(pos[0], 5, pos[1]);
849
+ scene.add(top);
850
+
851
+ collisionObjects.push({
852
+ position: new THREE.Vector3(pos[0], 2.5, pos[1]),
853
+ radius: 1.2
854
+ });
855
  });
856
  }
857
 
858
+ function createDecorations() {
859
+ const skullGeometry = new THREE.SphereGeometry(0.3, 8, 8);
860
+ const boneMaterial = new THREE.MeshStandardMaterial({
861
+ color: 0xd4c4a8,
862
+ roughness: 0.9
 
 
 
 
 
863
  });
 
 
 
 
864
 
865
+ for (let i = 0; i < 30; i++) {
866
+ const skull = new THREE.Mesh(skullGeometry, boneMaterial);
867
+ skull.position.set(
868
+ (Math.random() - 0.5) * 80,
869
+ 0.15,
870
+ (Math.random() - 0.5) * 80
871
+ );
872
+ skull.rotation.set(Math.random() * 0.5, Math.random() * Math.PI * 2, 0);
873
+ skull.scale.setScalar(0.5 + Math.random() * 0.5);
874
+ scene.add(skull);
875
+ }
876
 
877
+ const torchPositions = [
878
+ [-20, 2.5, -19], [20, 2.5, 21], [-29, 2.5, 10], [29, 2.5, -9],
879
+ [-48, 2.5, -48], [48, 2.5, -48], [-48, 2.5, 48], [48, 2.5, 48]
880
+ ];
881
 
882
+ torchPositions.forEach(pos => {
883
+ const torchLight = new THREE.PointLight(0xff4400, 2, 15);
884
+ torchLight.position.set(pos[0], pos[1] + 1, pos[2]);
885
+ scene.add(torchLight);
886
+ pointLights.push(torchLight);
887
+
888
+ const flameGeometry = new THREE.ConeGeometry(0.15, 0.4, 8);
889
+ const flameMaterial = new THREE.MeshBasicMaterial({
890
+ color: 0xff6600,
891
+ transparent: true,
892
+ opacity: 0.8
893
+ });
894
+ const flame = new THREE.Mesh(flameGeometry, flameMaterial);
895
+ flame.position.set(pos[0], pos[1] + 1.2, pos[2]);
896
+ scene.add(flame);
897
+ });
898
+ }
899
+
900
+ function createLighting() {
901
+ const ambientLight = new THREE.AmbientLight(0x201010, 0.3);
902
+ scene.add(ambientLight);
903
 
904
+ const mainLight = new THREE.DirectionalLight(0xff6644, 0.4);
905
+ mainLight.position.set(10, 20, 10);
906
+ mainLight.castShadow = true;
907
+ mainLight.shadow.mapSize.width = 2048;
908
+ mainLight.shadow.mapSize.height = 2048;
909
+ mainLight.shadow.camera.near = 0.5;
910
+ mainLight.shadow.camera.far = 50;
911
+ mainLight.shadow.camera.left = -30;
912
+ mainLight.shadow.camera.right = 30;
913
+ mainLight.shadow.camera.top = 30;
914
+ mainLight.shadow.camera.bottom = -30;
915
+ mainLight.shadow.bias = -0.0001;
916
+ scene.add(mainLight);
917
 
918
+ const hemisphereLight = new THREE.HemisphereLight(0x443322, 0x111111, 0.3);
919
+ scene.add(hemisphereLight);
920
+ }
921
+
922
+ function createAmbientParticles() {
923
+ const particleCount = 200;
924
+ const geometry = new THREE.BufferGeometry();
925
+ const positions = new Float32Array(particleCount * 3);
926
 
927
+ for (let i = 0; i < particleCount * 3; i += 3) {
928
+ positions[i] = (Math.random() - 0.5) * 100;
929
+ positions[i + 1] = Math.random() * 5;
930
+ positions[i + 2] = (Math.random() - 0.5) * 100;
931
+ }
932
 
933
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
 
 
934
 
935
+ const material = new THREE.PointsMaterial({
936
+ color: 0xff3300,
937
+ size: 0.05,
938
+ transparent: true,
939
+ opacity: 0.6,
940
+ blending: THREE.AdditiveBlending
941
+ });
942
 
943
+ const particleSystem = new THREE.Points(geometry, material);
944
+ scene.add(particleSystem);
945
+ ambientParticles.push(particleSystem);
946
+ }
947
+
948
+ function setupMinimap() {
949
+ const canvas = document.getElementById('minimapCanvas');
950
+ canvas.width = 150;
951
+ canvas.height = 150;
952
+ minimapCtx = canvas.getContext('2d');
953
+ }
954
+
955
+ function updateMinimap() {
956
+ if (!minimapCtx) return;
957
 
958
+ minimapCtx.fillStyle = 'rgba(0, 0, 0, 0.8)';
959
+ minimapCtx.fillRect(0, 0, 150, 150);
 
 
 
960
 
961
+ minimapCtx.strokeStyle = '#330000';
962
+ minimapCtx.lineWidth = 1;
963
+ minimapCtx.strokeRect(0, 0, 150, 150);
964
 
965
+ const scale = 1.5;
966
+ const offsetX = 75 - player.position.x * scale;
967
+ const offsetZ = 75 - player.position.z * scale;
 
968
 
969
+ minimapCtx.fillStyle = '#00ff00';
970
+ minimapCtx.beginPath();
971
+ minimapCtx.arc(75, 75, 4, 0, Math.PI * 2);
972
+ minimapCtx.fill();
973
 
974
+ minimapCtx.strokeStyle = '#00ff00';
975
+ minimapCtx.beginPath();
976
+ minimapCtx.moveTo(75, 75);
977
+ const angle = player.rotation.y;
978
+ minimapCtx.lineTo(75 - Math.sin(angle) * 10, 75 - Math.cos(angle) * 10);
979
+ minimapCtx.stroke();
 
 
 
 
 
 
980
 
981
+ enemies.forEach(enemy => {
982
+ const ex = enemy.mesh.position.x * scale + offsetX;
983
+ const ez = enemy.mesh.position.z * scale + offsetZ;
984
+
985
+ if (ex >= 0 && ex <= 150 && ez >= 0 && ez <= 150) {
986
+ minimapCtx.fillStyle = '#ff0000';
987
+ minimapCtx.beginPath();
988
+ minimapCtx.arc(ex, ez, 3, 0, Math.PI * 2);
989
+ minimapCtx.fill();
990
+ }
991
  });
992
  }
993
 
994
+ function setupControls() {
 
995
  if (!isMobile) {
996
+ document.addEventListener('keydown', onKeyDown);
997
+ document.addEventListener('keyup', onKeyUp);
998
+ document.addEventListener('mousedown', onMouseDown);
999
+ document.addEventListener('mouseup', onMouseUp);
1000
+ document.addEventListener('mousemove', onMouseMove);
1001
+ document.addEventListener('click', () => {
1002
+ if (gameActive && !isPointerLocked) {
1003
+ renderer.domElement.requestPointerLock();
1004
+ }
1005
+ });
1006
+ document.addEventListener('pointerlockchange', () => {
1007
+ isPointerLocked = document.pointerLockElement === renderer.domElement;
1008
+ });
1009
+ } else {
1010
+ setupMobileControls();
1011
  }
1012
  }
1013
 
1014
+ function setupMobileControls() {
1015
+ const moveJoystick = document.getElementById('moveJoystick');
1016
+ const moveStick = document.getElementById('moveStick');
1017
+ const lookJoystick = document.getElementById('lookJoystick');
1018
+ const lookStick = document.getElementById('lookStick');
1019
+ const shootButton = document.getElementById('shootButton');
1020
+ const reloadButton = document.getElementById('reloadButton');
1021
 
1022
+ let moveTouchId = null;
1023
+ let lookTouchId = null;
1024
+
1025
+ moveJoystick.addEventListener('touchstart', (e) => {
1026
+ e.preventDefault();
1027
+ const touch = e.changedTouches[0];
1028
+ moveTouchId = touch.identifier;
1029
+ moveJoystickData.active = true;
1030
+ updateJoystick(touch, moveJoystick, moveStick, moveJoystickData);
1031
  });
 
1032
 
1033
+ moveJoystick.addEventListener('touchmove', (e) => {
1034
+ e.preventDefault();
1035
+ for (let touch of e.changedTouches) {
1036
+ if (touch.identifier === moveTouchId) {
1037
+ updateJoystick(touch, moveJoystick, moveStick, moveJoystickData);
1038
+ }
1039
+ }
1040
  });
 
1041
 
1042
+ moveJoystick.addEventListener('touchend', (e) => {
1043
+ for (let touch of e.changedTouches) {
1044
+ if (touch.identifier === moveTouchId) {
1045
+ moveTouchId = null;
1046
+ moveJoystickData.active = false;
1047
+ moveJoystickData.x = 0;
1048
+ moveJoystickData.y = 0;
1049
+ moveStick.style.transform = 'translate(-50%, -50%)';
1050
+ moveForward = moveBackward = moveLeft = moveRight = false;
1051
+ }
1052
+ }
1053
+ });
1054
 
1055
+ lookJoystick.addEventListener('touchstart', (e) => {
1056
+ e.preventDefault();
1057
+ const touch = e.changedTouches[0];
1058
+ lookTouchId = touch.identifier;
1059
+ lookJoystickData.active = true;
1060
+ lookJoystickData.lastX = touch.clientX;
1061
+ lookJoystickData.lastY = touch.clientY;
1062
+ });
1063
 
1064
+ lookJoystick.addEventListener('touchmove', (e) => {
1065
+ e.preventDefault();
1066
+ for (let touch of e.changedTouches) {
1067
+ if (touch.identifier === lookTouchId) {
1068
+ const deltaX = touch.clientX - lookJoystickData.lastX;
1069
+ const deltaY = touch.clientY - lookJoystickData.lastY;
1070
+ lookJoystickData.x = deltaX * 0.01;
1071
+ lookJoystickData.y = deltaY * 0.01;
1072
+ lookJoystickData.lastX = touch.clientX;
1073
+ lookJoystickData.lastY = touch.clientY;
1074
+
1075
+ const rect = lookJoystick.getBoundingClientRect();
1076
+ const centerX = rect.left + rect.width / 2;
1077
+ const centerY = rect.top + rect.height / 2;
1078
+ let dx = touch.clientX - centerX;
1079
+ let dy = touch.clientY - centerY;
1080
+ const dist = Math.sqrt(dx * dx + dy * dy);
1081
+ const maxDist = 45;
1082
+ if (dist > maxDist) {
1083
+ dx = dx * maxDist / dist;
1084
+ dy = dy * maxDist / dist;
1085
+ }
1086
+ lookStick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
1087
+ }
1088
+ }
1089
+ });
1090
+
1091
+ lookJoystick.addEventListener('touchend', (e) => {
1092
+ for (let touch of e.changedTouches) {
1093
+ if (touch.identifier === lookTouchId) {
1094
+ lookTouchId = null;
1095
+ lookJoystickData.active = false;
1096
+ lookJoystickData.x = 0;
1097
+ lookJoystickData.y = 0;
1098
+ lookStick.style.transform = 'translate(-50%, -50%)';
1099
+ }
1100
+ }
1101
+ });
1102
+
1103
+ shootButton.addEventListener('touchstart', (e) => {
1104
+ e.preventDefault();
1105
+ if (gameActive) shoot();
1106
+ });
1107
+
1108
+ reloadButton.addEventListener('touchstart', (e) => {
1109
+ e.preventDefault();
1110
+ if (gameActive) reload();
1111
+ });
1112
+
1113
+ function updateJoystick(touch, container, stick, data) {
1114
+ const rect = container.getBoundingClientRect();
1115
+ const centerX = rect.left + rect.width / 2;
1116
+ const centerY = rect.top + rect.height / 2;
1117
+
1118
+ let dx = touch.clientX - centerX;
1119
+ let dy = touch.clientY - centerY;
1120
+
1121
+ const dist = Math.sqrt(dx * dx + dy * dy);
1122
+ const maxDist = 45;
1123
+
1124
+ if (dist > maxDist) {
1125
+ dx = dx * maxDist / dist;
1126
+ dy = dy * maxDist / dist;
1127
+ }
1128
+
1129
+ data.x = dx / maxDist;
1130
+ data.y = dy / maxDist;
1131
+
1132
+ stick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
1133
+
1134
+ const threshold = 0.3;
1135
+ moveForward = data.y < -threshold;
1136
+ moveBackward = data.y > threshold;
1137
+ moveLeft = data.x < -threshold;
1138
+ moveRight = data.x > threshold;
1139
  }
 
1140
  }
1141
 
1142
+ function onKeyDown(e) {
1143
+ if (!gameActive) return;
1144
+ switch (e.code) {
1145
+ case 'KeyW': case 'ArrowUp': moveForward = true; break;
1146
+ case 'KeyS': case 'ArrowDown': moveBackward = true; break;
1147
+ case 'KeyA': case 'ArrowLeft': moveLeft = true; break;
1148
+ case 'KeyD': case 'ArrowRight': moveRight = true; break;
1149
+ case 'KeyR': reload(); break;
1150
  }
 
 
 
 
 
 
 
 
1151
  }
1152
 
1153
+ function onKeyUp(e) {
1154
+ switch (e.code) {
1155
+ case 'KeyW': case 'ArrowUp': moveForward = false; break;
1156
+ case 'KeyS': case 'ArrowDown': moveBackward = false; break;
1157
+ case 'KeyA': case 'ArrowLeft': moveLeft = false; break;
1158
+ case 'KeyD': case 'ArrowRight': moveRight = false; break;
1159
  }
1160
  }
1161
 
1162
+ function onMouseDown(e) {
1163
+ if (e.button === 0 && gameActive && isPointerLocked) shoot();
 
 
 
 
 
1164
  }
1165
 
1166
+ function onMouseUp(e) {}
 
 
 
 
 
 
 
 
1167
 
1168
+ function onMouseMove(e) {
1169
+ if (!gameActive || !isPointerLocked) return;
1170
+ mouseMovement.x = e.movementX || 0;
1171
+ mouseMovement.y = e.movementY || 0;
 
 
 
 
1172
  }
1173
 
1174
  function shoot() {
1175
+ if (!canShoot || ammo <= 0 || isReloading) return;
1176
+
1177
  ammo--;
1178
  updateUI();
1179
+ canShoot = false;
1180
+ setTimeout(() => canShoot = true, shotCooldown);
1181
 
1182
+ weaponRecoil = 0.3;
1183
+ screenShake.intensity = 0.05;
1184
+
1185
+ const bulletGeometry = new THREE.SphereGeometry(0.08, 8, 8);
1186
+ const bulletMaterial = new THREE.MeshBasicMaterial({
1187
+ color: 0xffff00,
1188
+ emissive: 0xffaa00
1189
+ });
1190
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1191
 
1192
+ const bulletPos = new THREE.Vector3();
1193
+ camera.getWorldPosition(bulletPos);
1194
+ bullet.position.copy(bulletPos);
1195
 
 
1196
  const direction = new THREE.Vector3(0, 0, -1);
1197
  direction.applyQuaternion(camera.quaternion);
1198
 
1199
+ const spread = 0.02;
1200
+ direction.x += (Math.random() - 0.5) * spread;
1201
+ direction.y += (Math.random() - 0.5) * spread;
1202
+ direction.normalize();
1203
+
1204
  scene.add(bullet);
1205
+
1206
+ const bulletLight = new THREE.PointLight(0xffff00, 1, 5);
1207
+ bullet.add(bulletLight);
1208
+
1209
  bullets.push({
1210
  mesh: bullet,
1211
  direction: direction,
1212
+ speed: 2,
1213
  distance: 0
1214
  });
1215
 
1216
+ createMuzzleFlash();
1217
+ createShellCasing();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
 
1219
+ if (ammo === 0 && reserveAmmo > 0) {
1220
+ setTimeout(reload, 500);
1221
+ }
1222
  }
1223
 
1224
+ function createMuzzleFlash() {
1225
+ const flashGeometry = new THREE.SphereGeometry(0.3, 8, 8);
1226
+ const flashMaterial = new THREE.MeshBasicMaterial({
1227
+ color: 0xffaa00,
1228
+ transparent: true,
1229
+ opacity: 1
 
 
 
1230
  });
1231
+ const flash = new THREE.Mesh(flashGeometry, flashMaterial);
1232
+
1233
+ const pos = new THREE.Vector3(0, -0.1, -1);
1234
+ pos.applyQuaternion(camera.quaternion);
1235
+ camera.getWorldPosition(flash.position);
1236
+ flash.position.add(pos);
1237
+
1238
+ scene.add(flash);
1239
+
1240
+ const flashLight = new THREE.PointLight(0xffaa00, 3, 10);
1241
+ flash.add(flashLight);
1242
+
1243
+ let opacity = 1;
1244
+ const fadeFlash = () => {
1245
+ opacity -= 0.2;
1246
+ flashMaterial.opacity = opacity;
1247
+ flashLight.intensity = opacity * 3;
1248
+ if (opacity > 0) {
1249
+ requestAnimationFrame(fadeFlash);
1250
+ } else {
1251
+ scene.remove(flash);
1252
+ }
1253
+ };
1254
+ fadeFlash();
1255
+ }
1256
+
1257
+ function createShellCasing() {
1258
+ const shellGeometry = new THREE.CylinderGeometry(0.02, 0.02, 0.1, 8);
1259
+ const shellMaterial = new THREE.MeshStandardMaterial({ color: 0xccaa00 });
1260
+ const shell = new THREE.Mesh(shellGeometry, shellMaterial);
1261
+
1262
+ const pos = new THREE.Vector3(0.3, -0.2, -0.5);
1263
+ pos.applyQuaternion(camera.quaternion);
1264
+ camera.getWorldPosition(shell.position);
1265
+ shell.position.add(pos);
1266
 
1267
+ scene.add(shell);
1268
+
1269
+ const velocity = new THREE.Vector3(
1270
+ (Math.random() - 0.3) * 0.1,
1271
+ 0.1,
1272
+ (Math.random() - 0.5) * 0.05
1273
+ );
1274
+ velocity.applyQuaternion(camera.quaternion);
1275
+
1276
+ const animate = () => {
1277
+ velocity.y -= 0.005;
1278
+ shell.position.add(velocity);
1279
+ shell.rotation.x += 0.2;
1280
+ shell.rotation.z += 0.1;
1281
+
1282
+ if (shell.position.y > 0) {
1283
+ requestAnimationFrame(animate);
1284
+ } else {
1285
+ setTimeout(() => scene.remove(shell), 3000);
1286
  }
1287
+ };
1288
+ animate();
1289
+ }
1290
+
1291
+ function reload() {
1292
+ if (isReloading || ammo === maxAmmo || reserveAmmo <= 0) return;
1293
+
1294
+ isReloading = true;
1295
+
1296
+ setTimeout(() => {
1297
+ const needed = maxAmmo - ammo;
1298
+ const toReload = Math.min(needed, reserveAmmo);
1299
+ ammo += toReload;
1300
+ reserveAmmo -= toReload;
1301
+ isReloading = false;
1302
+ updateUI();
1303
+ }, 1500);
1304
+ }
1305
+
1306
+ function createDemon() {
1307
+ const demon = new THREE.Group();
1308
+
1309
+ const bodyGeometry = new THREE.SphereGeometry(0.7, 16, 16);
1310
+ const bodyMaterial = new THREE.MeshStandardMaterial({
1311
+ color: 0x660000,
1312
+ roughness: 0.6,
1313
+ metalness: 0.2,
1314
+ emissive: 0x220000
1315
  });
1316
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
1317
+ body.position.y = 1.2;
1318
+ body.castShadow = true;
1319
+ demon.add(body);
1320
 
1321
+ const headGeometry = new THREE.SphereGeometry(0.5, 16, 16);
1322
+ const headMaterial = new THREE.MeshStandardMaterial({
1323
+ color: 0x880000,
1324
+ roughness: 0.5,
1325
+ metalness: 0.3,
1326
+ emissive: 0x330000
 
 
1327
  });
1328
+ const head = new THREE.Mesh(headGeometry, headMaterial);
1329
+ head.position.y = 2;
1330
+ head.castShadow = true;
1331
+ demon.add(head);
1332
 
1333
+ const hornGeometry = new THREE.ConeGeometry(0.08, 0.6, 8);
1334
+ const hornMaterial = new THREE.MeshStandardMaterial({
1335
+ color: 0x222222,
1336
+ roughness: 0.3,
1337
+ metalness: 0.7
 
 
 
1338
  });
1339
 
1340
+ const horn1 = new THREE.Mesh(hornGeometry, hornMaterial);
1341
+ horn1.position.set(-0.25, 2.4, 0);
1342
+ horn1.rotation.z = 0.3;
1343
+ demon.add(horn1);
1344
+
1345
+ const horn2 = new THREE.Mesh(hornGeometry, hornMaterial);
1346
+ horn2.position.set(0.25, 2.4, 0);
1347
+ horn2.rotation.z = -0.3;
1348
+ demon.add(horn2);
1349
+
1350
+ const eyeGeometry = new THREE.SphereGeometry(0.1, 8, 8);
1351
+ const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
1352
+
1353
+ const eye1 = new THREE.Mesh(eyeGeometry, eyeMaterial);
1354
+ eye1.position.set(-0.2, 2.1, 0.4);
1355
+ demon.add(eye1);
1356
+
1357
+ const eye2 = new THREE.Mesh(eyeGeometry, eyeMaterial);
1358
+ eye2.position.set(0.2, 2.1, 0.4);
1359
+ demon.add(eye2);
1360
+
1361
+ const eyeLight = new THREE.PointLight(0xffff00, 0.5, 3);
1362
+ eyeLight.position.set(0, 2.1, 0.5);
1363
+ demon.add(eyeLight);
1364
+
1365
+ const armGeometry = new THREE.CylinderGeometry(0.1, 0.15, 0.8, 8);
1366
+ const armMaterial = new THREE.MeshStandardMaterial({ color: 0x550000 });
1367
+
1368
+ const arm1 = new THREE.Mesh(armGeometry, armMaterial);
1369
+ arm1.position.set(-0.8, 1.2, 0);
1370
+ arm1.rotation.z = Math.PI / 4;
1371
+ demon.add(arm1);
1372
+
1373
+ const arm2 = new THREE.Mesh(armGeometry, armMaterial);
1374
+ arm2.position.set(0.8, 1.2, 0);
1375
+ arm2.rotation.z = -Math.PI / 4;
1376
+ demon.add(arm2);
1377
+
1378
+ const clawGeometry = new THREE.ConeGeometry(0.08, 0.3, 6);
1379
+ const clawMaterial = new THREE.MeshStandardMaterial({ color: 0x111111 });
1380
+
1381
+ for (let i = 0; i < 3; i++) {
1382
+ const claw1 = new THREE.Mesh(clawGeometry, clawMaterial);
1383
+ claw1.position.set(-1.1 + i * 0.1, 0.9, 0);
1384
+ claw1.rotation.z = Math.PI;
1385
+ demon.add(claw1);
1386
+
1387
+ const claw2 = new THREE.Mesh(clawGeometry, clawMaterial);
1388
+ claw2.position.set(1.1 - i * 0.1, 0.9, 0);
1389
+ claw2.rotation.z = Math.PI;
1390
+ demon.add(claw2);
1391
+ }
1392
+
1393
+ const legGeometry = new THREE.CylinderGeometry(0.15, 0.1, 0.8, 8);
1394
+
1395
+ const leg1 = new THREE.Mesh(legGeometry, armMaterial);
1396
+ leg1.position.set(-0.3, 0.4, 0);
1397
+ demon.add(leg1);
1398
+
1399
+ const leg2 = new THREE.Mesh(legGeometry, armMaterial);
1400
+ leg2.position.set(0.3, 0.4, 0);
1401
+ demon.add(leg2);
1402
+
1403
+ return demon;
1404
+ }
1405
+
1406
+ function spawnEnemy() {
1407
+ const demon = createDemon();
1408
+
1409
+ let x, z;
1410
+ do {
1411
+ const angle = Math.random() * Math.PI * 2;
1412
+ const dist = 20 + Math.random() * 25;
1413
+ x = player.position.x + Math.cos(angle) * dist;
1414
+ z = player.position.z + Math.sin(angle) * dist;
1415
+ } while (Math.abs(x) > 45 || Math.abs(z) > 45);
1416
+
1417
+ demon.position.set(x, 0, z);
1418
+ scene.add(demon);
1419
+
1420
+ const baseSpeed = 0.03 + wave * 0.005;
1421
+ const baseHealth = 80 + wave * 20;
1422
+
1423
+ enemies.push({
1424
+ mesh: demon,
1425
+ health: baseHealth,
1426
+ maxHealth: baseHealth,
1427
+ speed: baseSpeed + Math.random() * 0.02,
1428
+ lastAttack: 0,
1429
+ attackCooldown: 800 + Math.random() * 400,
1430
+ damage: 8 + wave * 2,
1431
+ animTime: Math.random() * Math.PI * 2
1432
+ });
1433
+ }
1434
+
1435
+ function createBloodParticles(position, count = 20) {
1436
+ for (let i = 0; i < count; i++) {
1437
+ const geometry = new THREE.SphereGeometry(0.05 + Math.random() * 0.1, 6, 6);
1438
+ const material = new THREE.MeshBasicMaterial({
1439
+ color: new THREE.Color(0.5 + Math.random() * 0.3, 0, 0),
1440
+ transparent: true,
1441
+ opacity: 1
1442
+ });
1443
+ const particle = new THREE.Mesh(geometry, material);
1444
+ particle.position.copy(position);
1445
+ particle.position.y += 1 + Math.random();
1446
 
1447
+ scene.add(particle);
 
1448
 
1449
+ const velocity = new THREE.Vector3(
1450
+ (Math.random() - 0.5) * 0.2,
1451
+ Math.random() * 0.15,
1452
+ (Math.random() - 0.5) * 0.2
1453
+ );
 
1454
 
1455
+ particles.push({
1456
+ mesh: particle,
1457
+ velocity: velocity,
1458
+ life: 1,
1459
+ decay: 0.02 + Math.random() * 0.02
1460
+ });
1461
+ }
1462
+ }
1463
+
1464
+ function createExplosion(position) {
1465
+ for (let i = 0; i < 30; i++) {
1466
+ const geometry = new THREE.SphereGeometry(0.1 + Math.random() * 0.2, 6, 6);
1467
+ const material = new THREE.MeshBasicMaterial({
1468
+ color: new THREE.Color(1, Math.random() * 0.5, 0),
1469
+ transparent: true,
1470
+ opacity: 1
1471
+ });
1472
+ const particle = new THREE.Mesh(geometry, material);
1473
+ particle.position.copy(position);
1474
+ particle.position.y += 1;
1475
 
1476
+ scene.add(particle);
1477
+
1478
+ const velocity = new THREE.Vector3(
1479
+ (Math.random() - 0.5) * 0.3,
1480
+ Math.random() * 0.2,
1481
+ (Math.random() - 0.5) * 0.3
1482
+ );
1483
+
1484
+ particles.push({
1485
+ mesh: particle,
1486
+ velocity: velocity,
1487
+ life: 1,
1488
+ decay: 0.03
1489
+ });
1490
  }
1491
+
1492
+ const explosionLight = new THREE.PointLight(0xff6600, 5, 20);
1493
+ explosionLight.position.copy(position);
1494
+ explosionLight.position.y += 1;
1495
+ scene.add(explosionLight);
1496
+
1497
+ let intensity = 5;
1498
+ const fadeLight = () => {
1499
+ intensity -= 0.3;
1500
+ explosionLight.intensity = intensity;
1501
+ if (intensity > 0) {
1502
+ requestAnimationFrame(fadeLight);
1503
+ } else {
1504
+ scene.remove(explosionLight);
1505
+ }
1506
+ };
1507
+ fadeLight();
1508
  }
1509
 
1510
+ function startGame() {
1511
+ document.getElementById('startScreen').style.display = 'none';
1512
+ gameActive = true;
1513
+
1514
+ if (!isMobile) {
1515
+ renderer.domElement.requestPointerLock();
1516
+ }
1517
+
1518
+ for (let i = 0; i < 5 + wave; i++) {
1519
+ setTimeout(() => spawnEnemy(), i * 500);
1520
+ }
1521
+ }
1522
+
1523
+ function restartGame() {
1524
+ document.getElementById('gameOverScreen').style.display = 'none';
1525
+
1526
+ playerHealth = 100;
1527
+ playerArmor = 50;
1528
+ ammo = maxAmmo;
1529
+ reserveAmmo = 120;
1530
+ score = 0;
1531
+ kills = 0;
1532
+ wave = 1;
1533
+
1534
+ enemies.forEach(e => scene.remove(e.mesh));
1535
+ enemies = [];
1536
+
1537
+ bullets.forEach(b => scene.remove(b.mesh));
1538
+ bullets = [];
1539
+
1540
+ particles.forEach(p => scene.remove(p.mesh));
1541
+ particles = [];
1542
+
1543
+ player.position.set(0, 1.7, 0);
1544
+ player.rotation.set(0, 0, 0);
1545
+ camera.rotation.set(0, 0, 0);
1546
+
1547
+ updateUI();
1548
+
1549
+ if (!isMobile) {
1550
+ renderer.domElement.requestPointerLock();
1551
+ }
1552
+
1553
+ gameActive = true;
1554
+
1555
+ for (let i = 0; i < 5; i++) {
1556
+ setTimeout(() => spawnEnemy(), i * 500);
1557
+ }
1558
+ }
1559
+
1560
+ function gameOver() {
1561
+ gameActive = false;
1562
+
1563
+ if (document.pointerLockElement) {
1564
+ document.exitPointerLock();
1565
+ }
1566
+
1567
+ document.getElementById('finalScoreValue').textContent = score;
1568
+ document.getElementById('finalKillValue').textContent = kills;
1569
+ document.getElementById('gameOverScreen').style.display = 'flex';
1570
+ }
1571
+
1572
+ function updateUI() {
1573
+ document.getElementById('healthFill').style.width = `${playerHealth}%`;
1574
+ document.getElementById('armorFill').style.width = `${playerArmor}%`;
1575
+ document.getElementById('ammoValue').textContent = ammo;
1576
+ document.getElementById('ammoMax').textContent = reserveAmmo;
1577
+ document.getElementById('scoreValue').textContent = score;
1578
+ document.getElementById('killValue').textContent = kills;
1579
+ document.getElementById('waveValue').textContent = wave;
1580
+
1581
+ const damageVignette = document.getElementById('damageVignette');
1582
+ damageVignette.style.opacity = Math.max(0, (50 - playerHealth) / 50) * 0.8;
1583
+ }
1584
+
1585
+ function checkCollision(newPos) {
1586
+ for (const obj of collisionObjects) {
1587
+ if (obj.radius) {
1588
+ const dist = newPos.distanceTo(obj.position);
1589
+ if (dist < obj.radius + 0.5) return true;
1590
+ }
1591
+ }
1592
+
1593
+ if (Math.abs(newPos.x) > 48 || Math.abs(newPos.z) > 48) return true;
1594
+
1595
+ return false;
1596
  }
1597
 
1598
  function animate() {
 
1601
  const delta = clock.getDelta();
1602
 
1603
  if (gameActive) {
1604
+ if (!isMobile && isPointerLocked) {
1605
+ player.rotation.y -= mouseMovement.x * 0.002;
1606
+ camera.rotation.x -= mouseMovement.y * 0.002;
1607
+ camera.rotation.x = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, camera.rotation.x));
1608
+ mouseMovement.x = 0;
1609
+ mouseMovement.y = 0;
 
 
 
 
 
 
 
 
 
1610
  }
1611
 
1612
+ if (isMobile && lookJoystickData.active) {
1613
+ player.rotation.y -= lookJoystickData.x * 2;
1614
+ camera.rotation.x -= lookJoystickData.y * 2;
1615
+ camera.rotation.x = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, camera.rotation.x));
1616
+ lookJoystickData.x *= 0.5;
1617
+ lookJoystickData.y *= 0.5;
1618
+ }
1619
 
1620
+ const speed = 0.15;
1621
+ const direction = new THREE.Vector3();
1622
+
1623
+ if (moveForward) direction.z -= 1;
1624
+ if (moveBackward) direction.z += 1;
1625
+ if (moveLeft) direction.x -= 1;
1626
+ if (moveRight) direction.x += 1;
1627
+
1628
+ if (direction.length() > 0) {
1629
+ direction.normalize();
1630
+ direction.applyAxisAngle(new THREE.Vector3(0, 1, 0), player.rotation.y);
1631
 
1632
+ const newPos = player.position.clone();
1633
+ newPos.add(direction.multiplyScalar(speed));
 
 
1634
 
1635
+ if (!checkCollision(newPos)) {
1636
+ player.position.copy(newPos);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1637
  }
1638
 
1639
+ weaponBob += delta * 10;
1640
+ }
 
 
 
 
 
 
 
 
 
 
 
1641
 
 
1642
  for (let i = bullets.length - 1; i >= 0; i--) {
1643
  const bullet = bullets[i];
1644
  bullet.mesh.position.add(bullet.direction.clone().multiplyScalar(bullet.speed));
1645
  bullet.distance += bullet.speed;
1646
 
1647
+ if (bullet.distance > 100) {
 
1648
  scene.remove(bullet.mesh);
1649
  bullets.splice(i, 1);
1650
  continue;
1651
  }
1652
 
 
1653
  for (let j = enemies.length - 1; j >= 0; j--) {
1654
  const enemy = enemies[j];
1655
+ const dist = bullet.mesh.position.distanceTo(enemy.mesh.position.clone().add(new THREE.Vector3(0, 1.2, 0)));
1656
 
1657
+ if (dist < 1.2) {
1658
+ const damage = 25 + Math.random() * 15;
1659
+ enemy.health -= damage;
1660
+
1661
  scene.remove(bullet.mesh);
1662
  bullets.splice(i, 1);
1663
 
1664
+ createBloodParticles(enemy.mesh.position);
 
 
 
 
 
 
1665
 
1666
+ if (enemy.health <= 0) {
1667
+ createExplosion(enemy.mesh.position);
1668
+ scene.remove(enemy.mesh);
1669
+ enemies.splice(j, 1);
1670
+ score += 100 * wave;
1671
+ kills++;
1672
+ updateUI();
1673
+
1674
+ if (Math.random() < 0.3) {
1675
+ playerHealth = Math.min(100, playerHealth + 10);
1676
+ }
1677
+ if (Math.random() < 0.2) {
1678
+ playerArmor = Math.min(100, playerArmor + 15);
1679
+ }
1680
+ if (Math.random() < 0.1) {
1681
+ reserveAmmo = Math.min(999, reserveAmmo + 30);
1682
+ }
1683
+ updateUI();
1684
+ }
1685
  break;
1686
  }
1687
  }
1688
  }
1689
 
1690
+ enemies.forEach((enemy, index) => {
1691
+ enemy.animTime += delta * 5;
1692
+
1693
+ const targetPos = player.position.clone();
1694
+ const direction = targetPos.sub(enemy.mesh.position).normalize();
1695
+
1696
+ enemy.mesh.position.add(direction.multiplyScalar(enemy.speed));
1697
+ enemy.mesh.lookAt(player.position.x, enemy.mesh.position.y, player.position.z);
1698
+
1699
+ enemy.mesh.position.y = Math.sin(enemy.animTime) * 0.1;
1700
+
1701
+ const dist = player.position.distanceTo(enemy.mesh.position);
1702
+
1703
+ if (dist < 2 && Date.now() - enemy.lastAttack > enemy.attackCooldown) {
1704
+ enemy.lastAttack = Date.now();
1705
+
1706
+ let damage = enemy.damage;
1707
+ if (playerArmor > 0) {
1708
+ const absorbed = Math.min(playerArmor, damage * 0.6);
1709
+ playerArmor -= absorbed;
1710
+ damage -= absorbed;
1711
+ }
1712
+ playerHealth -= damage;
1713
+
1714
+ updateUI();
1715
+
1716
+ screenShake.intensity = 0.1;
1717
+
1718
+ const hitEffect = document.getElementById('hitEffect');
1719
+ hitEffect.style.opacity = '1';
1720
+ setTimeout(() => hitEffect.style.opacity = '0', 150);
1721
+
1722
+ if (playerHealth <= 0) {
1723
+ gameOver();
1724
+ }
1725
+ }
1726
+ });
1727
+
1728
+ for (let i = particles.length - 1; i >= 0; i--) {
1729
+ const p = particles[i];
1730
+ p.velocity.y -= 0.01;
1731
+ p.mesh.position.add(p.velocity);
1732
+ p.life -= p.decay;
1733
+ p.mesh.material.opacity = p.life;
1734
+
1735
+ if (p.life <= 0 || p.mesh.position.y < 0) {
1736
+ scene.remove(p.mesh);
1737
+ particles.splice(i, 1);
1738
+ }
1739
+ }
1740
+
1741
+ if (enemies.length < 3 + wave) {
1742
+ if (Math.random() < 0.02) {
1743
+ spawnEnemy();
1744
+ }
1745
+ }
1746
+
1747
+ if (kills > 0 && kills % 10 === 0 && kills / 10 >= wave) {
1748
+ wave++;
1749
+ updateUI();
1750
+ for (let i = 0; i < 3; i++) {
1751
+ setTimeout(() => spawnEnemy(), i * 300);
1752
+ }
1753
+ }
1754
+
1755
+ if (screenShake.intensity > 0) {
1756
+ cameraShakeOffset.set(
1757
+ (Math.random() - 0.5) * screenShake.intensity,
1758
+ (Math.random() - 0.5) * screenShake.intensity,
1759
+ 0
1760
+ );
1761
+ camera.position.x = cameraShakeOffset.x;
1762
+ camera.position.z = cameraShakeOffset.z;
1763
+ screenShake.intensity *= screenShake.decay;
1764
+ if (screenShake.intensity < 0.001) {
1765
+ screenShake.intensity = 0;
1766
+ camera.position.x = 0;
1767
+ camera.position.z = 0;
1768
+ }
1769
  }
1770
+
1771
+ weaponRecoil *= 0.8;
1772
+
1773
+ pointLights.forEach((light, i) => {
1774
+ light.intensity = 1.5 + Math.sin(Date.now() * 0.01 + i) * 0.5;
1775
+ });
1776
+
1777
+ ambientParticles.forEach(ps => {
1778
+ const positions = ps.geometry.attributes.position.array;
1779
+ for (let i = 1; i < positions.length; i += 3) {
1780
+ positions[i] += 0.01;
1781
+ if (positions[i] > 5) positions[i] = 0;
1782
+ }
1783
+ ps.geometry.attributes.position.needsUpdate = true;
1784
+ });
1785
+
1786
+ updateMinimap();
1787
  }
1788
 
1789
+ composer.render();
1790
+ }
1791
+
1792
+ function onWindowResize() {
1793
+ camera.aspect = window.innerWidth / window.innerHeight;
1794
+ camera.updateProjectionMatrix();
1795
+ renderer.setSize(window.innerWidth, window.innerHeight);
1796
+ composer.setSize(window.innerWidth, window.innerHeight);
1797
  }
1798
 
1799
+ init();
 
1800
  </script>
1801
  </body>
1802
+ </html>