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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +721 -549
index.html CHANGED
@@ -1,10 +1,9 @@
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;
@@ -20,7 +19,7 @@
20
  body {
21
  overflow: hidden;
22
  background: #000;
23
- color: #ff5555;
24
  height: 100vh;
25
  height: 100dvh;
26
  }
@@ -59,16 +58,16 @@
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 {
@@ -91,22 +90,22 @@
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 {
@@ -116,16 +115,16 @@
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
  }
@@ -135,8 +134,8 @@
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
 
@@ -185,8 +184,8 @@
185
  top: 30px;
186
  left: 30px;
187
  font-size: 28px;
188
- color: #ff5555;
189
- text-shadow: 0 0 10px #ff0000;
190
  }
191
 
192
  #killCount {
@@ -194,8 +193,8 @@
194
  top: 70px;
195
  left: 30px;
196
  font-size: 18px;
197
- color: #ff9999;
198
- text-shadow: 0 0 5px #ff0000;
199
  }
200
 
201
  #waveInfo {
@@ -208,13 +207,22 @@
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;
@@ -226,26 +234,27 @@
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;
@@ -254,15 +263,15 @@
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 {
@@ -301,12 +310,12 @@
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;
@@ -315,13 +324,13 @@
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
 
@@ -373,8 +382,8 @@
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;
@@ -384,14 +393,14 @@
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 {
@@ -440,7 +449,7 @@
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
  }
@@ -535,6 +544,7 @@
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">
@@ -556,15 +566,15 @@
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>
@@ -584,36 +594,35 @@
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);
@@ -627,14 +636,12 @@
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
 
@@ -661,288 +668,404 @@
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() {
@@ -955,27 +1078,47 @@
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 => {
@@ -985,7 +1128,7 @@
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
  });
@@ -1179,14 +1322,11 @@
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();
@@ -1196,33 +1336,32 @@
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,
@@ -1230,21 +1369,21 @@
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 {
@@ -1254,40 +1393,6 @@
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
 
@@ -1300,172 +1405,187 @@
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
  });
@@ -1476,35 +1596,18 @@
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() {
@@ -1515,8 +1618,16 @@
1515
  renderer.domElement.requestPointerLock();
1516
  }
1517
 
1518
- for (let i = 0; i < 5 + wave; i++) {
1519
- setTimeout(() => spawnEnemy(), i * 500);
 
 
 
 
 
 
 
 
1520
  }
1521
  }
1522
 
@@ -1526,7 +1637,7 @@
1526
  playerHealth = 100;
1527
  playerArmor = 50;
1528
  ammo = maxAmmo;
1529
- reserveAmmo = 120;
1530
  score = 0;
1531
  kills = 0;
1532
  wave = 1;
@@ -1552,8 +1663,16 @@
1552
 
1553
  gameActive = true;
1554
 
1555
- for (let i = 0; i < 5; i++) {
1556
- setTimeout(() => spawnEnemy(), i * 500);
 
 
 
 
 
 
 
 
1557
  }
1558
  }
1559
 
@@ -1577,6 +1696,7 @@
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;
@@ -1585,20 +1705,32 @@
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() {
1599
  requestAnimationFrame(animate);
1600
 
1601
  const delta = clock.getDelta();
 
1602
 
1603
  if (gameActive) {
1604
  if (!isMobile && isPointerLocked) {
@@ -1617,7 +1749,7 @@
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;
@@ -1634,9 +1766,21 @@
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--) {
@@ -1644,7 +1788,7 @@
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;
@@ -1652,10 +1796,12 @@
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);
@@ -1664,21 +1810,21 @@
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
  }
@@ -1688,15 +1834,50 @@
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
 
@@ -1705,7 +1886,7 @@
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
  }
@@ -1713,7 +1894,7 @@
1713
 
1714
  updateUI();
1715
 
1716
- screenShake.intensity = 0.1;
1717
 
1718
  const hitEffect = document.getElementById('hitEffect');
1719
  hitEffect.style.opacity = '1';
@@ -1727,7 +1908,7 @@
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;
@@ -1738,17 +1919,19 @@
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
 
@@ -1768,19 +1951,8 @@
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();
@@ -1799,4 +1971,4 @@
1799
  init();
1800
  </script>
1801
  </body>
1802
- </html>
 
 
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>ZOMBIE CITY - Survival Shooter</title>
7
  <style>
8
  * {
9
  margin: 0;
 
19
  body {
20
  overflow: hidden;
21
  background: #000;
22
+ color: #00ff00;
23
  height: 100vh;
24
  height: 100dvh;
25
  }
 
58
  transform: translate(-50%, -50%);
59
  width: 4px;
60
  height: 4px;
61
+ background: #00ff00;
62
  border-radius: 50%;
63
+ box-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00;
64
  }
65
 
66
  #crosshair::before, #crosshair::after {
67
  content: '';
68
  position: absolute;
69
+ background: #00ff00;
70
+ box-shadow: 0 0 5px #00ff00;
71
  }
72
 
73
  #crosshair::before {
 
90
  position: absolute;
91
  width: 15px;
92
  height: 2px;
93
+ background: #00ff00;
94
  top: 50%;
95
  right: 15px;
96
  transform: translateY(-50%);
97
+ box-shadow: 0 0 5px #00ff00;
98
  }
99
 
100
  .crosshair-bottom {
101
  position: absolute;
102
  width: 2px;
103
  height: 15px;
104
+ background: #00ff00;
105
  bottom: -20px;
106
  left: 50%;
107
  transform: translateX(-50%);
108
+ box-shadow: 0 0 5px #00ff00;
109
  }
110
 
111
  #healthBar {
 
115
  width: 250px;
116
  height: 25px;
117
  background: rgba(0, 0, 0, 0.8);
118
+ border: 2px solid #00ff00;
119
  border-radius: 3px;
120
  overflow: hidden;
121
+ box-shadow: 0 0 15px rgba(0, 255, 0, 0.5), inset 0 0 10px rgba(0, 0, 0, 0.5);
122
  }
123
 
124
  #healthFill {
125
  height: 100%;
126
  width: 100%;
127
+ background: linear-gradient(180deg, #66ff66 0%, #00cc00 50%, #009900 100%);
128
  transition: width 0.3s ease-out;
129
  box-shadow: inset 0 2px 5px rgba(255, 255, 255, 0.3);
130
  }
 
134
  bottom: 60px;
135
  left: 30px;
136
  font-size: 14px;
137
+ color: #99ff99;
138
+ text-shadow: 0 0 5px #00ff00;
139
  letter-spacing: 2px;
140
  }
141
 
 
184
  top: 30px;
185
  left: 30px;
186
  font-size: 28px;
187
+ color: #00ff00;
188
+ text-shadow: 0 0 10px #00ff00;
189
  }
190
 
191
  #killCount {
 
193
  top: 70px;
194
  left: 30px;
195
  font-size: 18px;
196
+ color: #99ff99;
197
+ text-shadow: 0 0 5px #00ff00;
198
  }
199
 
200
  #waveInfo {
 
207
  text-align: right;
208
  }
209
 
210
+ #zombieCount {
211
+ position: absolute;
212
+ top: 60px;
213
+ right: 30px;
214
+ font-size: 16px;
215
+ color: #ff9999;
216
+ text-shadow: 0 0 5px #ff0000;
217
+ }
218
+
219
  #startScreen {
220
  position: absolute;
221
  top: 0;
222
  left: 0;
223
  width: 100%;
224
  height: 100%;
225
+ background: radial-gradient(ellipse at center, rgba(0, 30, 0, 0.9) 0%, rgba(0, 0, 0, 0.95) 100%);
226
  display: flex;
227
  flex-direction: column;
228
  justify-content: center;
 
234
  #title {
235
  font-size: 80px;
236
  margin-bottom: 20px;
237
+ color: #00ff00;
238
+ text-shadow: 0 0 20px #00ff00, 0 0 40px #00ff00, 0 0 60px #00ff00;
239
  letter-spacing: 10px;
240
  animation: pulse 2s infinite;
241
  }
242
 
243
  @keyframes pulse {
244
+ 0%, 100% { text-shadow: 0 0 20px #00ff00, 0 0 40px #00ff00; }
245
+ 50% { text-shadow: 0 0 30px #00ff00, 0 0 60px #00ff00, 0 0 80px #00ff00; }
246
  }
247
 
248
  #subtitle {
249
  font-size: 24px;
250
  margin-bottom: 60px;
251
+ color: #99ff99;
252
  letter-spacing: 5px;
253
  }
254
 
255
  #startButton {
256
+ background: linear-gradient(180deg, #33ff33 0%, #00cc00 100%);
257
+ color: #000;
258
  border: none;
259
  padding: 20px 60px;
260
  font-size: 28px;
 
263
  text-transform: uppercase;
264
  letter-spacing: 5px;
265
  font-weight: bold;
266
+ box-shadow: 0 0 30px #00ff00, inset 0 2px 10px rgba(255, 255, 255, 0.3);
267
  transition: all 0.3s;
268
  pointer-events: auto;
269
  }
270
 
271
  #startButton:hover {
272
+ background: linear-gradient(180deg, #55ff55 0%, #22ee22 100%);
273
  transform: scale(1.1);
274
+ box-shadow: 0 0 50px #00ff00;
275
  }
276
 
277
  #gameOverScreen {
 
310
  #finalKills {
311
  font-size: 24px;
312
  margin-bottom: 40px;
313
+ color: #99ff99;
314
  }
315
 
316
  #restartButton {
317
+ background: linear-gradient(180deg, #33ff33 0%, #00cc00 100%);
318
+ color: #000;
319
  border: none;
320
  padding: 20px 60px;
321
  font-size: 28px;
 
324
  text-transform: uppercase;
325
  letter-spacing: 5px;
326
  font-weight: bold;
327
+ box-shadow: 0 0 30px #00ff00;
328
  transition: all 0.3s;
329
  pointer-events: auto;
330
  }
331
 
332
  #restartButton:hover {
333
+ background: linear-gradient(180deg, #55ff55 0%, #22ee22 100%);
334
  transform: scale(1.1);
335
  }
336
 
 
382
  .joystickBase {
383
  width: 150px;
384
  height: 150px;
385
+ background: radial-gradient(circle, rgba(0, 255, 0, 0.3) 0%, rgba(0, 200, 0, 0.1) 100%);
386
+ border: 3px solid rgba(0, 255, 0, 0.5);
387
  border-radius: 50%;
388
  position: absolute;
389
  top: 0;
 
393
  .joystickStick {
394
  width: 60px;
395
  height: 60px;
396
+ background: radial-gradient(circle, rgba(100, 255, 100, 0.9) 0%, rgba(0, 200, 0, 0.8) 100%);
397
+ border: 2px solid rgba(150, 255, 150, 0.8);
398
  border-radius: 50%;
399
  position: absolute;
400
  top: 50%;
401
  left: 50%;
402
  transform: translate(-50%, -50%);
403
+ box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
404
  }
405
 
406
  #shootButton {
 
449
  width: 150px;
450
  height: 150px;
451
  background: rgba(0, 0, 0, 0.7);
452
+ border: 2px solid #00ff00;
453
  border-radius: 5px;
454
  overflow: hidden;
455
  }
 
544
  <div id="score">SCORE: <span id="scoreValue">0</span></div>
545
  <div id="killCount">KILLS: <span id="killValue">0</span></div>
546
  <div id="waveInfo">WAVE: <span id="waveValue">1</span></div>
547
+ <div id="zombieCount">ZOMBIES: <span id="zombieValue">0</span></div>
548
  <div id="hitEffect"></div>
549
  <div id="damageVignette"></div>
550
  <div id="minimap">
 
566
  </div>
567
 
568
  <div id="startScreen">
569
+ <h1 id="title">ZOMBIE CITY</h1>
570
+ <p id="subtitle">SURVIVE THE UNDEAD APOCALYPSE</p>
571
+ <button id="startButton">START SURVIVAL</button>
572
  </div>
573
 
574
  <div id="gameOverScreen">
575
+ <h1 id="gameOverTitle">YOU DIED</h1>
576
  <p id="finalScore">SCORE: <span id="finalScoreValue">0</span></p>
577
+ <p id="finalKills">ZOMBIES KILLED: <span id="finalKillValue">0</span></p>
578
  <button id="restartButton">TRY AGAIN</button>
579
  </div>
580
  </div>
 
594
  import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
595
  import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
596
  import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
 
597
  import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
598
 
599
  let scene, camera, renderer, composer;
600
+ let player, enemies = [], bullets = [], particles = [];
601
+ let playerHealth = 100, playerArmor = 50, ammo = 30, maxAmmo = 30, reserveAmmo = 200;
602
  let score = 0, kills = 0, wave = 1;
603
  let gameActive = false, isReloading = false;
604
  let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
605
+ let canShoot = true, shotCooldown = 80;
606
  let clock = new THREE.Clock();
607
  let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
608
  let screenShake = { intensity: 0, decay: 0.9 };
 
609
  let cameraShakeOffset = new THREE.Vector3();
610
  let weaponBob = 0, weaponRecoil = 0;
611
  let moveJoystickData = { active: false, x: 0, y: 0 };
612
  let lookJoystickData = { active: false, x: 0, y: 0, lastX: 0, lastY: 0 };
613
  let mouseMovement = { x: 0, y: 0 };
614
  let isPointerLocked = false;
615
+ let streetLights = [];
616
+ let cars = [];
617
+ let buildings = [];
 
618
  let collisionObjects = [];
619
+ let minimapCtx;
620
+ let spawnPoints = [];
621
 
622
  function init() {
623
  scene = new THREE.Scene();
624
+ scene.background = new THREE.Color(0x1a2a3a);
625
+ scene.fog = new THREE.FogExp2(0x1a2a3a, 0.008);
626
 
627
  camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.1, 1000);
628
  camera.position.set(0, 1.7, 0);
 
636
  renderer.shadowMap.enabled = true;
637
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
638
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
639
+ renderer.toneMappingExposure = 1.0;
640
  document.getElementById('gameCanvas').appendChild(renderer.domElement);
641
 
642
  setupPostProcessing();
643
+ createCity();
 
644
  createLighting();
 
645
  setupControls();
646
  setupMinimap();
647
 
 
668
 
669
  const bloomPass = new UnrealBloomPass(
670
  new THREE.Vector2(window.innerWidth, window.innerHeight),
671
+ 0.3, 0.4, 0.85
672
  );
673
  composer.addPass(bloomPass);
674
 
675
  const smaaPass = new SMAAPass(window.innerWidth, window.innerHeight);
676
  composer.addPass(smaaPass);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  }
678
 
679
+ function createCity() {
680
+ const groundGeometry = new THREE.PlaneGeometry(500, 500, 100, 100);
681
+ const groundMaterial = new THREE.MeshStandardMaterial({
682
+ color: 0x2a2a2a,
683
+ roughness: 0.9,
684
+ metalness: 0.1
685
+ });
686
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
687
+ ground.rotation.x = -Math.PI / 2;
688
+ ground.receiveShadow = true;
689
+ scene.add(ground);
690
+
691
+ createRoads();
692
+ createBuildings();
693
+ createCars();
694
+ createStreetLights();
695
+ createProps();
696
+ createSpawnPoints();
697
+ }
698
+
699
+ function createRoads() {
700
+ const roadMaterial = new THREE.MeshStandardMaterial({
701
+ color: 0x333333,
702
+ roughness: 0.8
703
+ });
704
 
705
+ const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
706
+ const whiteLine = new THREE.MeshBasicMaterial({ color: 0xffffff });
707
 
708
+ for (let i = -2; i <= 2; i++) {
709
+ const roadH = new THREE.Mesh(
710
+ new THREE.PlaneGeometry(500, 15),
711
+ roadMaterial
712
+ );
713
+ roadH.rotation.x = -Math.PI / 2;
714
+ roadH.position.set(0, 0.01, i * 80);
715
+ scene.add(roadH);
716
+
717
+ for (let j = -25; j <= 25; j++) {
718
+ const line = new THREE.Mesh(
719
+ new THREE.PlaneGeometry(4, 0.3),
720
+ lineMaterial
721
+ );
722
+ line.rotation.x = -Math.PI / 2;
723
+ line.position.set(j * 10, 0.02, i * 80);
724
+ scene.add(line);
725
  }
726
+
727
+ const roadV = new THREE.Mesh(
728
+ new THREE.PlaneGeometry(15, 500),
729
+ roadMaterial
730
+ );
731
+ roadV.rotation.x = -Math.PI / 2;
732
+ roadV.position.set(i * 80, 0.01, 0);
733
+ scene.add(roadV);
734
  }
735
 
736
+ const sidewalkMaterial = new THREE.MeshStandardMaterial({
737
+ color: 0x666666,
738
+ roughness: 0.7
739
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
740
 
741
+ for (let i = -2; i <= 2; i++) {
742
+ for (let side = -1; side <= 1; side += 2) {
743
+ const sidewalkH = new THREE.Mesh(
744
+ new THREE.BoxGeometry(500, 0.15, 3),
745
+ sidewalkMaterial
746
+ );
747
+ sidewalkH.position.set(0, 0.075, i * 80 + side * 9);
748
+ sidewalkH.receiveShadow = true;
749
+ scene.add(sidewalkH);
750
+ }
751
+ }
752
  }
753
 
754
+ function createBuildings() {
755
+ const buildingColors = [0x4a5568, 0x5a6578, 0x3a4558, 0x6a7588, 0x2d3748];
 
 
 
 
 
 
756
 
757
+ const gridPositions = [];
758
+ for (let x = -3; x <= 3; x++) {
759
+ for (let z = -3; z <= 3; z++) {
760
+ if (Math.abs(x) <= 2 && Math.abs(z) <= 2) {
761
+ if (x !== 0 && z !== 0) {
762
+ gridPositions.push({ x: x * 80 - 35, z: z * 80 - 35 });
763
+ gridPositions.push({ x: x * 80 + 35, z: z * 80 - 35 });
764
+ gridPositions.push({ x: x * 80 - 35, z: z * 80 + 35 });
765
+ gridPositions.push({ x: x * 80 + 35, z: z * 80 + 35 });
766
+ }
767
+ }
768
+ }
769
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
 
771
+ gridPositions.forEach(pos => {
772
+ const width = 20 + Math.random() * 25;
773
+ const depth = 20 + Math.random() * 25;
774
+ const height = 15 + Math.random() * 60;
775
+
776
+ const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
777
+ const buildingMaterial = new THREE.MeshStandardMaterial({
778
+ color: buildingColors[Math.floor(Math.random() * buildingColors.length)],
779
+ roughness: 0.8,
780
+ metalness: 0.2
781
+ });
782
+
783
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
784
+ building.position.set(pos.x, height / 2, pos.z);
785
+ building.castShadow = true;
786
+ building.receiveShadow = true;
787
+ scene.add(building);
788
+ buildings.push(building);
789
 
790
  collisionObjects.push({
791
+ position: new THREE.Vector3(pos.x, height / 2, pos.z),
792
+ width: width,
793
+ depth: depth
794
+ });
795
+
796
+ const windowMaterial = new THREE.MeshBasicMaterial({
797
+ color: Math.random() > 0.3 ? 0xffffaa : 0x333333
798
  });
799
+
800
+ const windowRows = Math.floor(height / 4);
801
+ const windowCols = Math.floor(width / 4);
802
+
803
+ for (let row = 0; row < windowRows; row++) {
804
+ for (let col = 0; col < windowCols; col++) {
805
+ if (Math.random() > 0.2) {
806
+ const windowGeom = new THREE.PlaneGeometry(1.5, 2);
807
+ const windowMat = new THREE.MeshBasicMaterial({
808
+ color: Math.random() > 0.4 ? 0xffffcc : 0x222222
809
+ });
810
+
811
+ const windowMesh = new THREE.Mesh(windowGeom, windowMat);
812
+ windowMesh.position.set(
813
+ pos.x - width/2 + 2 + col * 4,
814
+ 2 + row * 4,
815
+ pos.z + depth/2 + 0.1
816
+ );
817
+ scene.add(windowMesh);
818
+
819
+ const windowMesh2 = windowMesh.clone();
820
+ windowMesh2.position.z = pos.z - depth/2 - 0.1;
821
+ windowMesh2.rotation.y = Math.PI;
822
+ scene.add(windowMesh2);
823
+ }
824
+ }
825
+ }
826
  });
 
 
 
827
  }
828
 
829
+ function createCars() {
830
+ const carColors = [0xff0000, 0x0000ff, 0x00ff00, 0xffff00, 0xff00ff, 0x00ffff, 0xffffff, 0x333333];
831
+
832
+ const carPositions = [
833
+ { x: 20, z: 80, rot: 0 },
834
+ { x: -30, z: 80, rot: 0 },
835
+ { x: 50, z: -80, rot: Math.PI },
836
+ { x: -60, z: -80, rot: Math.PI },
837
+ { x: 80, z: 30, rot: Math.PI / 2 },
838
+ { x: 80, z: -40, rot: Math.PI / 2 },
839
+ { x: -80, z: 20, rot: -Math.PI / 2 },
840
+ { x: -80, z: -50, rot: -Math.PI / 2 },
841
+ { x: 10, z: 0, rot: 0 },
842
+ { x: -25, z: 0, rot: Math.PI },
843
+ { x: 0, z: 25, rot: Math.PI / 2 },
844
+ { x: 0, z: -30, rot: -Math.PI / 2 }
845
  ];
846
 
847
+ carPositions.forEach(pos => {
848
+ const carGroup = new THREE.Group();
849
+
850
+ const bodyGeometry = new THREE.BoxGeometry(4, 1.2, 2);
851
+ const bodyMaterial = new THREE.MeshStandardMaterial({
852
+ color: carColors[Math.floor(Math.random() * carColors.length)],
853
+ roughness: 0.3,
854
+ metalness: 0.8
855
+ });
856
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
857
+ body.position.y = 0.8;
858
+ body.castShadow = true;
859
+ carGroup.add(body);
860
+
861
+ const topGeometry = new THREE.BoxGeometry(2.5, 1, 1.8);
862
+ const topMaterial = new THREE.MeshStandardMaterial({
863
+ color: 0x88ccff,
864
+ roughness: 0.1,
865
+ metalness: 0.9,
866
+ transparent: true,
867
+ opacity: 0.7
868
+ });
869
+ const top = new THREE.Mesh(topGeometry, topMaterial);
870
+ top.position.set(-0.3, 1.8, 0);
871
+ carGroup.add(top);
872
+
873
+ const wheelGeometry = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 16);
874
+ const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x111111 });
875
+
876
+ const wheelPositions = [
877
+ { x: 1.3, y: 0.4, z: 1 },
878
+ { x: 1.3, y: 0.4, z: -1 },
879
+ { x: -1.3, y: 0.4, z: 1 },
880
+ { x: -1.3, y: 0.4, z: -1 }
881
+ ];
882
+
883
+ wheelPositions.forEach(wPos => {
884
+ const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
885
+ wheel.rotation.x = Math.PI / 2;
886
+ wheel.position.set(wPos.x, wPos.y, wPos.z);
887
+ carGroup.add(wheel);
888
+ });
889
+
890
+ const headlightGeom = new THREE.SphereGeometry(0.15, 8, 8);
891
+ const headlightMat = new THREE.MeshBasicMaterial({ color: 0xffffcc });
892
 
893
+ const hl1 = new THREE.Mesh(headlightGeom, headlightMat);
894
+ hl1.position.set(2, 0.8, 0.6);
895
+ carGroup.add(hl1);
896
+
897
+ const hl2 = new THREE.Mesh(headlightGeom, headlightMat);
898
+ hl2.position.set(2, 0.8, -0.6);
899
+ carGroup.add(hl2);
900
+
901
+ carGroup.position.set(pos.x, 0, pos.z);
902
+ carGroup.rotation.y = pos.rot;
903
+ scene.add(carGroup);
904
+ cars.push(carGroup);
905
 
906
  collisionObjects.push({
907
+ position: new THREE.Vector3(pos.x, 1, pos.z),
908
+ width: 5,
909
+ depth: 2.5,
910
+ rotation: pos.rot
911
  });
912
  });
913
  }
914
 
915
+ function createStreetLights() {
916
+ const lightPositions = [];
 
 
 
 
917
 
918
+ for (let x = -200; x <= 200; x += 40) {
919
+ for (let z = -2; z <= 2; z++) {
920
+ lightPositions.push({ x: x, z: z * 80 + 11 });
921
+ lightPositions.push({ x: x, z: z * 80 - 11 });
922
+ }
 
 
 
 
 
923
  }
924
 
925
+ for (let z = -200; z <= 200; z += 40) {
926
+ for (let x = -2; x <= 2; x++) {
927
+ lightPositions.push({ x: x * 80 + 11, z: z });
928
+ lightPositions.push({ x: x * 80 - 11, z: z });
929
+ }
930
+ }
931
 
932
+ const uniquePositions = [];
933
+ lightPositions.forEach(pos => {
934
+ const exists = uniquePositions.some(p =>
935
+ Math.abs(p.x - pos.x) < 5 && Math.abs(p.z - pos.z) < 5
936
+ );
937
+ if (!exists && Math.abs(pos.x) < 200 && Math.abs(pos.z) < 200) {
938
+ uniquePositions.push(pos);
939
+ }
940
+ });
941
+
942
+ uniquePositions.slice(0, 80).forEach(pos => {
943
+ const poleGeometry = new THREE.CylinderGeometry(0.1, 0.15, 6, 8);
944
+ const poleMaterial = new THREE.MeshStandardMaterial({ color: 0x444444 });
945
+ const pole = new THREE.Mesh(poleGeometry, poleMaterial);
946
+ pole.position.set(pos.x, 3, pos.z);
947
+ pole.castShadow = true;
948
+ scene.add(pole);
949
 
950
+ const armGeometry = new THREE.BoxGeometry(2, 0.1, 0.1);
951
+ const arm = new THREE.Mesh(armGeometry, poleMaterial);
952
+ arm.position.set(pos.x + 1, 6, pos.z);
953
+ scene.add(arm);
954
+
955
+ const lampGeometry = new THREE.SphereGeometry(0.3, 8, 8);
956
+ const lampMaterial = new THREE.MeshBasicMaterial({ color: 0xffffcc });
957
+ const lamp = new THREE.Mesh(lampGeometry, lampMaterial);
958
+ lamp.position.set(pos.x + 2, 5.8, pos.z);
959
+ scene.add(lamp);
960
+
961
+ const streetLight = new THREE.PointLight(0xffffcc, 1, 25);
962
+ streetLight.position.set(pos.x + 2, 5.5, pos.z);
963
+ streetLight.castShadow = true;
964
+ streetLight.shadow.mapSize.width = 512;
965
+ streetLight.shadow.mapSize.height = 512;
966
+ scene.add(streetLight);
967
+ streetLights.push(streetLight);
968
+
969
+ collisionObjects.push({
970
+ position: new THREE.Vector3(pos.x, 3, pos.z),
971
+ radius: 0.3
972
  });
 
 
 
973
  });
974
  }
975
 
976
+ function createProps() {
977
+ const trashCanGeometry = new THREE.CylinderGeometry(0.4, 0.35, 1, 12);
978
+ const trashCanMaterial = new THREE.MeshStandardMaterial({ color: 0x228822 });
979
 
980
+ for (let i = 0; i < 30; i++) {
981
+ const trashCan = new THREE.Mesh(trashCanGeometry, trashCanMaterial);
982
+ const angle = Math.random() * Math.PI * 2;
983
+ const dist = 20 + Math.random() * 150;
984
+ trashCan.position.set(
985
+ Math.cos(angle) * dist,
986
+ 0.5,
987
+ Math.sin(angle) * dist
988
+ );
989
+ trashCan.castShadow = true;
990
+ scene.add(trashCan);
991
+ }
 
 
 
 
 
 
 
 
 
 
992
 
993
+ const benchGeometry = new THREE.BoxGeometry(2, 0.5, 0.5);
994
+ const benchMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });
995
+
996
+ for (let i = 0; i < 20; i++) {
997
+ const bench = new THREE.Mesh(benchGeometry, benchMaterial);
998
+ const angle = Math.random() * Math.PI * 2;
999
+ const dist = 30 + Math.random() * 120;
1000
+ bench.position.set(
1001
+ Math.cos(angle) * dist,
1002
+ 0.5,
1003
+ Math.sin(angle) * dist
1004
+ );
1005
+ bench.rotation.y = Math.random() * Math.PI;
1006
+ bench.castShadow = true;
1007
+ scene.add(bench);
1008
  }
1009
 
1010
+ const barrelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1.2, 12);
1011
+ const barrelMaterial = new THREE.MeshStandardMaterial({ color: 0x884422 });
1012
 
1013
+ for (let i = 0; i < 15; i++) {
1014
+ const barrel = new THREE.Mesh(barrelGeometry, barrelMaterial);
1015
+ const angle = Math.random() * Math.PI * 2;
1016
+ const dist = 25 + Math.random() * 100;
1017
+ barrel.position.set(
1018
+ Math.cos(angle) * dist,
1019
+ 0.6,
1020
+ Math.sin(angle) * dist
1021
+ );
1022
+ if (Math.random() > 0.7) {
1023
+ barrel.rotation.x = Math.PI / 2;
1024
+ barrel.position.y = 0.5;
1025
+ }
1026
+ barrel.castShadow = true;
1027
+ scene.add(barrel);
1028
+ }
1029
+ }
1030
+
1031
+ function createSpawnPoints() {
1032
+ for (let x = -2; x <= 2; x++) {
1033
+ for (let z = -2; z <= 2; z++) {
1034
+ if (x !== 0 || z !== 0) {
1035
+ spawnPoints.push(new THREE.Vector3(x * 60, 0, z * 60));
1036
+ }
1037
+ }
1038
+ }
1039
+
1040
+ spawnPoints.push(new THREE.Vector3(100, 0, 0));
1041
+ spawnPoints.push(new THREE.Vector3(-100, 0, 0));
1042
+ spawnPoints.push(new THREE.Vector3(0, 0, 100));
1043
+ spawnPoints.push(new THREE.Vector3(0, 0, -100));
1044
+ spawnPoints.push(new THREE.Vector3(100, 0, 100));
1045
+ spawnPoints.push(new THREE.Vector3(-100, 0, -100));
1046
+ spawnPoints.push(new THREE.Vector3(100, 0, -100));
1047
+ spawnPoints.push(new THREE.Vector3(-100, 0, 100));
1048
+ }
1049
+
1050
+ function createLighting() {
1051
+ const ambientLight = new THREE.AmbientLight(0x404060, 0.4);
1052
+ scene.add(ambientLight);
1053
 
1054
+ const moonLight = new THREE.DirectionalLight(0x6688cc, 0.5);
1055
+ moonLight.position.set(50, 100, 50);
1056
+ moonLight.castShadow = true;
1057
+ moonLight.shadow.mapSize.width = 4096;
1058
+ moonLight.shadow.mapSize.height = 4096;
1059
+ moonLight.shadow.camera.near = 0.5;
1060
+ moonLight.shadow.camera.far = 300;
1061
+ moonLight.shadow.camera.left = -150;
1062
+ moonLight.shadow.camera.right = 150;
1063
+ moonLight.shadow.camera.top = 150;
1064
+ moonLight.shadow.camera.bottom = -150;
1065
+ scene.add(moonLight);
1066
+
1067
+ const hemisphereLight = new THREE.HemisphereLight(0x6688aa, 0x222233, 0.3);
1068
+ scene.add(hemisphereLight);
1069
  }
1070
 
1071
  function setupMinimap() {
 
1078
  function updateMinimap() {
1079
  if (!minimapCtx) return;
1080
 
1081
+ minimapCtx.fillStyle = 'rgba(20, 30, 40, 0.9)';
1082
  minimapCtx.fillRect(0, 0, 150, 150);
1083
 
1084
+ const scale = 0.4;
1085
+ const offsetX = 75;
1086
+ const offsetZ = 75;
1087
+
1088
+ minimapCtx.strokeStyle = '#333';
1089
+ minimapCtx.lineWidth = 2;
1090
+ for (let i = -2; i <= 2; i++) {
1091
+ minimapCtx.beginPath();
1092
+ minimapCtx.moveTo(0, 75 + i * 80 * scale);
1093
+ minimapCtx.lineTo(150, 75 + i * 80 * scale);
1094
+ minimapCtx.stroke();
1095
+
1096
+ minimapCtx.beginPath();
1097
+ minimapCtx.moveTo(75 + i * 80 * scale, 0);
1098
+ minimapCtx.lineTo(75 + i * 80 * scale, 150);
1099
+ minimapCtx.stroke();
1100
+ }
1101
 
1102
+ minimapCtx.fillStyle = '#555';
1103
+ buildings.forEach(b => {
1104
+ const bx = b.position.x * scale + offsetX;
1105
+ const bz = b.position.z * scale + offsetZ;
1106
+ minimapCtx.fillRect(bx - 5, bz - 5, 10, 10);
1107
+ });
1108
 
1109
  minimapCtx.fillStyle = '#00ff00';
1110
+ const px = player.position.x * scale + offsetX;
1111
+ const pz = player.position.z * scale + offsetZ;
1112
  minimapCtx.beginPath();
1113
+ minimapCtx.arc(px, pz, 4, 0, Math.PI * 2);
1114
  minimapCtx.fill();
1115
 
1116
  minimapCtx.strokeStyle = '#00ff00';
1117
+ minimapCtx.lineWidth = 2;
1118
  minimapCtx.beginPath();
1119
+ minimapCtx.moveTo(px, pz);
1120
  const angle = player.rotation.y;
1121
+ minimapCtx.lineTo(px - Math.sin(angle) * 10, pz - Math.cos(angle) * 10);
1122
  minimapCtx.stroke();
1123
 
1124
  enemies.forEach(enemy => {
 
1128
  if (ex >= 0 && ex <= 150 && ez >= 0 && ez <= 150) {
1129
  minimapCtx.fillStyle = '#ff0000';
1130
  minimapCtx.beginPath();
1131
+ minimapCtx.arc(ex, ez, 2, 0, Math.PI * 2);
1132
  minimapCtx.fill();
1133
  }
1134
  });
 
1322
  canShoot = false;
1323
  setTimeout(() => canShoot = true, shotCooldown);
1324
 
1325
+ weaponRecoil = 0.2;
1326
+ screenShake.intensity = 0.03;
1327
 
1328
+ const bulletGeometry = new THREE.SphereGeometry(0.06, 8, 8);
1329
+ const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
 
 
 
1330
  const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
1331
 
1332
  const bulletPos = new THREE.Vector3();
 
1336
  const direction = new THREE.Vector3(0, 0, -1);
1337
  direction.applyQuaternion(camera.quaternion);
1338
 
1339
+ const spread = 0.015;
1340
  direction.x += (Math.random() - 0.5) * spread;
1341
  direction.y += (Math.random() - 0.5) * spread;
1342
  direction.normalize();
1343
 
1344
  scene.add(bullet);
1345
 
1346
+ const bulletLight = new THREE.PointLight(0xffff00, 0.5, 3);
1347
  bullet.add(bulletLight);
1348
 
1349
  bullets.push({
1350
  mesh: bullet,
1351
  direction: direction,
1352
+ speed: 3,
1353
  distance: 0
1354
  });
1355
 
1356
  createMuzzleFlash();
 
1357
 
1358
  if (ammo === 0 && reserveAmmo > 0) {
1359
+ setTimeout(reload, 300);
1360
  }
1361
  }
1362
 
1363
  function createMuzzleFlash() {
1364
+ const flashGeometry = new THREE.SphereGeometry(0.2, 8, 8);
1365
  const flashMaterial = new THREE.MeshBasicMaterial({
1366
  color: 0xffaa00,
1367
  transparent: true,
 
1369
  });
1370
  const flash = new THREE.Mesh(flashGeometry, flashMaterial);
1371
 
1372
+ const pos = new THREE.Vector3(0.2, -0.1, -0.8);
1373
  pos.applyQuaternion(camera.quaternion);
1374
  camera.getWorldPosition(flash.position);
1375
  flash.position.add(pos);
1376
 
1377
  scene.add(flash);
1378
 
1379
+ const flashLight = new THREE.PointLight(0xffaa00, 2, 8);
1380
  flash.add(flashLight);
1381
 
1382
  let opacity = 1;
1383
  const fadeFlash = () => {
1384
+ opacity -= 0.25;
1385
  flashMaterial.opacity = opacity;
1386
+ flashLight.intensity = opacity * 2;
1387
  if (opacity > 0) {
1388
  requestAnimationFrame(fadeFlash);
1389
  } else {
 
1393
  fadeFlash();
1394
  }
1395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1396
  function reload() {
1397
  if (isReloading || ammo === maxAmmo || reserveAmmo <= 0) return;
1398
 
 
1405
  reserveAmmo -= toReload;
1406
  isReloading = false;
1407
  updateUI();
1408
+ }, 1200);
1409
  }
1410
 
1411
+ function createZombie() {
1412
+ const zombie = new THREE.Group();
1413
+
1414
+ const skinColors = [0x5a7a5a, 0x4a6a4a, 0x6a8a6a, 0x3a5a3a, 0x7a9a7a];
1415
+ const skinColor = skinColors[Math.floor(Math.random() * skinColors.length)];
1416
 
1417
+ const torsoGeometry = new THREE.BoxGeometry(0.8, 1.2, 0.5);
1418
+ const clothesMaterial = new THREE.MeshStandardMaterial({
1419
+ color: Math.random() > 0.5 ? 0x333344 : 0x443333,
1420
+ roughness: 0.9
 
 
1421
  });
1422
+ const torso = new THREE.Mesh(torsoGeometry, clothesMaterial);
1423
+ torso.position.y = 1.4;
1424
+ torso.castShadow = true;
1425
+ zombie.add(torso);
1426
+
1427
+ const headGeometry = new THREE.SphereGeometry(0.3, 12, 12);
1428
+ const skinMaterial = new THREE.MeshStandardMaterial({
1429
+ color: skinColor,
1430
+ roughness: 0.8
 
 
1431
  });
1432
+ const head = new THREE.Mesh(headGeometry, skinMaterial);
1433
+ head.position.y = 2.2;
1434
  head.castShadow = true;
1435
+ zombie.add(head);
 
 
 
 
 
 
 
 
 
 
 
 
1436
 
1437
+ const hairGeometry = new THREE.SphereGeometry(0.25, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2);
1438
+ const hairMaterial = new THREE.MeshStandardMaterial({ color: 0x222222 });
1439
+ const hair = new THREE.Mesh(hairGeometry, hairMaterial);
1440
+ hair.position.y = 2.35;
1441
+ hair.rotation.x = Math.random() * 0.3;
1442
+ zombie.add(hair);
1443
 
1444
+ const eyeGeometry = new THREE.SphereGeometry(0.05, 8, 8);
1445
  const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
1446
 
1447
  const eye1 = new THREE.Mesh(eyeGeometry, eyeMaterial);
1448
+ eye1.position.set(-0.1, 2.25, 0.25);
1449
+ zombie.add(eye1);
1450
 
1451
  const eye2 = new THREE.Mesh(eyeGeometry, eyeMaterial);
1452
+ eye2.position.set(0.1, 2.25, 0.25);
1453
+ zombie.add(eye2);
1454
+
1455
+ const mouthGeometry = new THREE.BoxGeometry(0.15, 0.05, 0.05);
1456
+ const mouthMaterial = new THREE.MeshBasicMaterial({ color: 0x440000 });
1457
+ const mouth = new THREE.Mesh(mouthGeometry, mouthMaterial);
1458
+ mouth.position.set(0, 2.1, 0.28);
1459
+ zombie.add(mouth);
1460
+
1461
+ const armGeometry = new THREE.BoxGeometry(0.2, 0.8, 0.2);
1462
+
1463
+ const armL = new THREE.Mesh(armGeometry, skinMaterial);
1464
+ armL.position.set(-0.6, 1.4, 0);
1465
+ armL.rotation.x = -0.5 + Math.random() * 0.3;
1466
+ armL.rotation.z = 0.2;
1467
+ armL.castShadow = true;
1468
+ zombie.add(armL);
1469
+
1470
+ const armR = new THREE.Mesh(armGeometry, skinMaterial);
1471
+ armR.position.set(0.6, 1.4, 0);
1472
+ armR.rotation.x = -0.5 + Math.random() * 0.3;
1473
+ armR.rotation.z = -0.2;
1474
+ armR.castShadow = true;
1475
+ zombie.add(armR);
1476
+
1477
+ const legGeometry = new THREE.BoxGeometry(0.25, 0.9, 0.25);
1478
+ const pantsMaterial = new THREE.MeshStandardMaterial({
1479
+ color: 0x222233,
1480
+ roughness: 0.9
1481
+ });
 
 
 
 
 
 
 
 
 
 
1482
 
1483
+ const legL = new THREE.Mesh(legGeometry, pantsMaterial);
1484
+ legL.position.set(-0.2, 0.45, 0);
1485
+ legL.castShadow = true;
1486
+ zombie.add(legL);
1487
+
1488
+ const legR = new THREE.Mesh(legGeometry, pantsMaterial);
1489
+ legR.position.set(0.2, 0.45, 0);
1490
+ legR.castShadow = true;
1491
+ zombie.add(legR);
1492
+
1493
+ if (Math.random() > 0.5) {
1494
+ const woundGeometry = new THREE.SphereGeometry(0.08, 6, 6);
1495
+ const woundMaterial = new THREE.MeshBasicMaterial({ color: 0x660000 });
1496
+ for (let i = 0; i < 3; i++) {
1497
+ const wound = new THREE.Mesh(woundGeometry, woundMaterial);
1498
+ wound.position.set(
1499
+ (Math.random() - 0.5) * 0.6,
1500
+ 1.2 + Math.random() * 0.8,
1501
+ 0.26
1502
+ );
1503
+ wound.scale.setScalar(0.5 + Math.random() * 0.5);
1504
+ zombie.add(wound);
1505
+ }
1506
+ }
1507
 
1508
+ return zombie;
1509
  }
1510
 
1511
+ function spawnZombieSwarm(position, count) {
1512
+ for (let i = 0; i < count; i++) {
1513
+ const zombie = createZombie();
1514
+
1515
+ const offsetX = (Math.random() - 0.5) * 15;
1516
+ const offsetZ = (Math.random() - 0.5) * 15;
1517
+
1518
+ zombie.position.set(
1519
+ position.x + offsetX,
1520
+ 0,
1521
+ position.z + offsetZ
1522
+ );
1523
+
1524
+ scene.add(zombie);
1525
+
1526
+ enemies.push({
1527
+ mesh: zombie,
1528
+ health: 30 + wave * 5,
1529
+ maxHealth: 30 + wave * 5,
1530
+ speed: 0.04 + Math.random() * 0.03 + wave * 0.003,
1531
+ lastAttack: 0,
1532
+ attackCooldown: 600 + Math.random() * 400,
1533
+ damage: 5 + wave,
1534
+ animTime: Math.random() * Math.PI * 2,
1535
+ walkCycle: Math.random() * Math.PI * 2
1536
+ });
1537
+ }
1538
 
1539
+ updateUI();
1540
+ }
1541
+
1542
+ function spawnRandomSwarm() {
1543
+ const validSpawnPoints = spawnPoints.filter(sp => {
1544
+ const dist = player.position.distanceTo(sp);
1545
+ return dist > 30 && dist < 150;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1546
  });
1547
+
1548
+ if (validSpawnPoints.length > 0) {
1549
+ const spawnPoint = validSpawnPoints[Math.floor(Math.random() * validSpawnPoints.length)];
1550
+ const swarmSize = 3 + Math.floor(Math.random() * 5) + Math.floor(wave / 2);
1551
+ spawnZombieSwarm(spawnPoint, swarmSize);
1552
+ }
1553
  }
1554
 
1555
+ function createBloodParticles(position, count = 15) {
1556
  for (let i = 0; i < count; i++) {
1557
+ const geometry = new THREE.SphereGeometry(0.04 + Math.random() * 0.06, 6, 6);
1558
  const material = new THREE.MeshBasicMaterial({
1559
+ color: new THREE.Color(0.4 + Math.random() * 0.2, 0, 0),
1560
  transparent: true,
1561
  opacity: 1
1562
  });
1563
  const particle = new THREE.Mesh(geometry, material);
1564
  particle.position.copy(position);
1565
+ particle.position.y += 1 + Math.random() * 0.5;
1566
 
1567
  scene.add(particle);
1568
 
1569
  const velocity = new THREE.Vector3(
1570
+ (Math.random() - 0.5) * 0.15,
1571
+ Math.random() * 0.1,
1572
+ (Math.random() - 0.5) * 0.15
1573
  );
1574
 
1575
  particles.push({
1576
  mesh: particle,
1577
  velocity: velocity,
1578
  life: 1,
1579
+ decay: 0.03 + Math.random() * 0.02
1580
  });
1581
  }
1582
  }
1583
 
1584
+ function createDeathEffect(position) {
1585
+ for (let i = 0; i < 20; i++) {
1586
+ const geometry = new THREE.SphereGeometry(0.08 + Math.random() * 0.1, 6, 6);
1587
  const material = new THREE.MeshBasicMaterial({
1588
+ color: new THREE.Color(0.5 + Math.random() * 0.3, 0.2, 0.1),
1589
  transparent: true,
1590
  opacity: 1
1591
  });
 
1596
  scene.add(particle);
1597
 
1598
  const velocity = new THREE.Vector3(
1599
+ (Math.random() - 0.5) * 0.2,
1600
+ Math.random() * 0.15,
1601
+ (Math.random() - 0.5) * 0.2
1602
  );
1603
 
1604
  particles.push({
1605
  mesh: particle,
1606
  velocity: velocity,
1607
  life: 1,
1608
+ decay: 0.025
1609
  });
1610
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1611
  }
1612
 
1613
  function startGame() {
 
1618
  renderer.domElement.requestPointerLock();
1619
  }
1620
 
1621
+ for (let i = 0; i < 4; i++) {
1622
+ setTimeout(() => {
1623
+ const angle = (i / 4) * Math.PI * 2;
1624
+ const spawnPos = new THREE.Vector3(
1625
+ Math.cos(angle) * 40,
1626
+ 0,
1627
+ Math.sin(angle) * 40
1628
+ );
1629
+ spawnZombieSwarm(spawnPos, 5 + wave);
1630
+ }, i * 800);
1631
  }
1632
  }
1633
 
 
1637
  playerHealth = 100;
1638
  playerArmor = 50;
1639
  ammo = maxAmmo;
1640
+ reserveAmmo = 200;
1641
  score = 0;
1642
  kills = 0;
1643
  wave = 1;
 
1663
 
1664
  gameActive = true;
1665
 
1666
+ for (let i = 0; i < 4; i++) {
1667
+ setTimeout(() => {
1668
+ const angle = (i / 4) * Math.PI * 2;
1669
+ const spawnPos = new THREE.Vector3(
1670
+ Math.cos(angle) * 40,
1671
+ 0,
1672
+ Math.sin(angle) * 40
1673
+ );
1674
+ spawnZombieSwarm(spawnPos, 5);
1675
+ }, i * 600);
1676
  }
1677
  }
1678
 
 
1696
  document.getElementById('scoreValue').textContent = score;
1697
  document.getElementById('killValue').textContent = kills;
1698
  document.getElementById('waveValue').textContent = wave;
1699
+ document.getElementById('zombieValue').textContent = enemies.length;
1700
 
1701
  const damageVignette = document.getElementById('damageVignette');
1702
  damageVignette.style.opacity = Math.max(0, (50 - playerHealth) / 50) * 0.8;
 
1705
  function checkCollision(newPos) {
1706
  for (const obj of collisionObjects) {
1707
  if (obj.radius) {
1708
+ const dist = new THREE.Vector2(newPos.x - obj.position.x, newPos.z - obj.position.z).length();
1709
  if (dist < obj.radius + 0.5) return true;
1710
+ } else if (obj.width && obj.depth) {
1711
+ const hw = obj.width / 2 + 0.5;
1712
+ const hd = obj.depth / 2 + 0.5;
1713
+
1714
+ if (Math.abs(newPos.x - obj.position.x) < hw &&
1715
+ Math.abs(newPos.z - obj.position.z) < hd) {
1716
+ return true;
1717
+ }
1718
  }
1719
  }
1720
 
1721
+ if (Math.abs(newPos.x) > 240 || Math.abs(newPos.z) > 240) return true;
1722
 
1723
  return false;
1724
  }
1725
 
1726
+ let lastSpawnTime = 0;
1727
+ let spawnInterval = 8000;
1728
+
1729
  function animate() {
1730
  requestAnimationFrame(animate);
1731
 
1732
  const delta = clock.getDelta();
1733
+ const time = clock.getElapsedTime();
1734
 
1735
  if (gameActive) {
1736
  if (!isMobile && isPointerLocked) {
 
1749
  lookJoystickData.y *= 0.5;
1750
  }
1751
 
1752
+ const speed = 0.18;
1753
  const direction = new THREE.Vector3();
1754
 
1755
  if (moveForward) direction.z -= 1;
 
1766
 
1767
  if (!checkCollision(newPos)) {
1768
  player.position.copy(newPos);
1769
+ } else {
1770
+ const newPosX = player.position.clone();
1771
+ newPosX.x += direction.x * speed / direction.length();
1772
+ if (!checkCollision(newPosX)) {
1773
+ player.position.x = newPosX.x;
1774
+ }
1775
+
1776
+ const newPosZ = player.position.clone();
1777
+ newPosZ.z += direction.z * speed / direction.length();
1778
+ if (!checkCollision(newPosZ)) {
1779
+ player.position.z = newPosZ.z;
1780
+ }
1781
  }
1782
 
1783
+ weaponBob += delta * 12;
1784
  }
1785
 
1786
  for (let i = bullets.length - 1; i >= 0; i--) {
 
1788
  bullet.mesh.position.add(bullet.direction.clone().multiplyScalar(bullet.speed));
1789
  bullet.distance += bullet.speed;
1790
 
1791
+ if (bullet.distance > 150) {
1792
  scene.remove(bullet.mesh);
1793
  bullets.splice(i, 1);
1794
  continue;
 
1796
 
1797
  for (let j = enemies.length - 1; j >= 0; j--) {
1798
  const enemy = enemies[j];
1799
+ const enemyCenter = enemy.mesh.position.clone();
1800
+ enemyCenter.y += 1.2;
1801
+ const dist = bullet.mesh.position.distanceTo(enemyCenter);
1802
 
1803
+ if (dist < 1) {
1804
+ const damage = 35 + Math.random() * 20;
1805
  enemy.health -= damage;
1806
 
1807
  scene.remove(bullet.mesh);
 
1810
  createBloodParticles(enemy.mesh.position);
1811
 
1812
  if (enemy.health <= 0) {
1813
+ createDeathEffect(enemy.mesh.position);
1814
  scene.remove(enemy.mesh);
1815
  enemies.splice(j, 1);
1816
+ score += 50 * wave;
1817
  kills++;
1818
  updateUI();
1819
 
1820
+ if (Math.random() < 0.25) {
1821
+ playerHealth = Math.min(100, playerHealth + 8);
1822
  }
1823
+ if (Math.random() < 0.15) {
1824
+ playerArmor = Math.min(100, playerArmor + 10);
1825
  }
1826
+ if (Math.random() < 0.2) {
1827
+ reserveAmmo = Math.min(999, reserveAmmo + 15);
1828
  }
1829
  updateUI();
1830
  }
 
1834
  }
1835
 
1836
  enemies.forEach((enemy, index) => {
1837
+ enemy.animTime += delta * 3;
1838
+ enemy.walkCycle += delta * 8;
1839
 
1840
  const targetPos = player.position.clone();
1841
+ const direction = new THREE.Vector3();
1842
+ direction.subVectors(targetPos, enemy.mesh.position).normalize();
1843
+
1844
+ const newEnemyPos = enemy.mesh.position.clone();
1845
+ newEnemyPos.add(direction.multiplyScalar(enemy.speed));
1846
+
1847
+ let canMove = true;
1848
+ for (const other of enemies) {
1849
+ if (other !== enemy) {
1850
+ const dist = newEnemyPos.distanceTo(other.mesh.position);
1851
+ if (dist < 1) {
1852
+ canMove = false;
1853
+ break;
1854
+ }
1855
+ }
1856
+ }
1857
+
1858
+ if (canMove) {
1859
+ enemy.mesh.position.copy(newEnemyPos);
1860
+ }
1861
 
 
1862
  enemy.mesh.lookAt(player.position.x, enemy.mesh.position.y, player.position.z);
1863
 
1864
+ const bobAmount = Math.sin(enemy.walkCycle) * 0.05;
1865
+ enemy.mesh.position.y = bobAmount;
1866
+
1867
+ const leftLeg = enemy.mesh.children[5];
1868
+ const rightLeg = enemy.mesh.children[6];
1869
+ const leftArm = enemy.mesh.children[3];
1870
+ const rightArm = enemy.mesh.children[4];
1871
+
1872
+ if (leftLeg && rightLeg) {
1873
+ leftLeg.rotation.x = Math.sin(enemy.walkCycle) * 0.4;
1874
+ rightLeg.rotation.x = Math.sin(enemy.walkCycle + Math.PI) * 0.4;
1875
+ }
1876
+
1877
+ if (leftArm && rightArm) {
1878
+ leftArm.rotation.x = -0.5 + Math.sin(enemy.walkCycle + Math.PI) * 0.2;
1879
+ rightArm.rotation.x = -0.5 + Math.sin(enemy.walkCycle) * 0.2;
1880
+ }
1881
 
1882
  const dist = player.position.distanceTo(enemy.mesh.position);
1883
 
 
1886
 
1887
  let damage = enemy.damage;
1888
  if (playerArmor > 0) {
1889
+ const absorbed = Math.min(playerArmor, damage * 0.5);
1890
  playerArmor -= absorbed;
1891
  damage -= absorbed;
1892
  }
 
1894
 
1895
  updateUI();
1896
 
1897
+ screenShake.intensity = 0.08;
1898
 
1899
  const hitEffect = document.getElementById('hitEffect');
1900
  hitEffect.style.opacity = '1';
 
1908
 
1909
  for (let i = particles.length - 1; i >= 0; i--) {
1910
  const p = particles[i];
1911
+ p.velocity.y -= 0.008;
1912
  p.mesh.position.add(p.velocity);
1913
  p.life -= p.decay;
1914
  p.mesh.material.opacity = p.life;
 
1919
  }
1920
  }
1921
 
1922
+ const now = Date.now();
1923
+ if (now - lastSpawnTime > spawnInterval && enemies.length < 50) {
1924
+ spawnRandomSwarm();
1925
+ lastSpawnTime = now;
1926
  }
1927
 
1928
+ if (kills > 0 && kills % 15 === 0 && kills / 15 >= wave) {
1929
  wave++;
1930
+ spawnInterval = Math.max(3000, 8000 - wave * 500);
1931
  updateUI();
1932
+
1933
  for (let i = 0; i < 3; i++) {
1934
+ setTimeout(() => spawnRandomSwarm(), i * 500);
1935
  }
1936
  }
1937
 
 
1951
  }
1952
  }
1953
 
1954
+ streetLights.forEach((light, i) => {
1955
+ light.intensity = 0.8 + Math.sin(time * 2 + i * 0.5) * 0.2;
 
 
 
 
 
 
 
 
 
 
 
1956
  });
1957
 
1958
  updateMinimap();
 
1971
  init();
1972
  </script>
1973
  </body>
1974
+ </html>