Guest User

Untitled

a guest
Sep 19th, 2025
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.82 KB | None | 0 0
  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" />
  6. <title>Mini Wolfenstein-style Raycaster — "Unscreenshottable" HUD Demo</title>
  7. <style>
  8. html,body{height:100%;margin:0;background:#111;color:#ddd;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
  9. #game{display:block;margin:0 auto;background:#000;image-rendering:pixelated}
  10. .info{max-width:900px;margin:12px auto;padding:8px;text-align:center}
  11. a{color:#7fd}
  12. </style>
  13. </head>
  14. <body>
  15. <div class="info">
  16. <strong>Mini raycaster demo</strong> — arrow keys / WASD to move, mouse to look, <kbd>H</kbd> toggles the "unscreenshottable" HUD. Open the file in a modern browser.
  17. </div>
  18. <canvas id="game" width="960" height="480"></canvas>
  19. <script>
  20. // Simple 2D raycaster + unscreenshottable dynamic-HUD overlay.
  21. // Single-file demo. Performance-minded but intentionally simple.
  22.  
  23. const canvas = document.getElementById('game');
  24. const ctx = canvas.getContext('2d');
  25. const W = canvas.width, H = canvas.height;
  26.  
  27. // ----- MAP -----
  28. // 1 = wall, 0 = empty
  29. const map = [
  30. 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  31. 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  32. 1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,
  33. 1,0,0,0,1,1,1,0,0,0,1,0,0,0,0,1,
  34. 1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,
  35. 1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,
  36. 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,
  37. 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,
  38. 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,
  39. 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,
  40. 1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,
  41. 1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
  42. 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  43. 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  44. 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  45. 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  46. ];
  47. const MAPW = 16, MAPH = 16;
  48.  
  49. // ----- PLAYER -----
  50. let px = 3.5, py = 3.5; // position
  51. let pa = 0; // angle radians
  52. let moveSpeed = 2.5; // units / sec
  53. let rotSpeed = 2.5; // rad / sec
  54.  
  55. // ----- INPUT -----
  56. const keys = {};
  57. document.addEventListener('keydown', e=>{ keys[e.key.toLowerCase()] = true; if(e.key==='h' || e.key==='H') { hudEnabled = !hudEnabled; } });
  58. document.addEventListener('keyup', e=>{ keys[e.key.toLowerCase()] = false; });
  59.  
  60. // Lock pointer for mouse-look
  61. canvas.addEventListener('click', ()=>{ canvas.requestPointerLock?.(); });
  62. let mouseDX = 0;
  63. function onPointerMove(e){ mouseDX += e.movementX || 0; }
  64. document.addEventListener('pointermove', onPointerMove);
  65.  
  66. // ----- RAYCAST SETTINGS -----
  67. const FOV = Math.PI/3; // 60deg
  68. const NUM_RAYS = W/2 | 0; // columns (reduce for perf)
  69. const DIST_PROJ_PLANE = (W/2) / Math.tan(FOV/2);
  70.  
  71. // ----- TEXTURES (simple color bands) -----
  72. function wallColor(id, side){
  73. if(id===1) return side? '#8888cc' : '#6666aa';
  74. if(id===2) return side? '#88cc88' : '#66aa66';
  75. return '#444';
  76. }
  77.  
  78. // ----- OFFSCREEN CANVASES FOR NOISE EFFECT -----
  79. const staticNoise = document.createElement('canvas');
  80. staticNoise.width = W; staticNoise.height = H;
  81. const sCtx = staticNoise.getContext('2d');
  82.  
  83. const dynamicNoise = document.createElement('canvas');
  84. dynamicNoise.width = W; dynamicNoise.height = H;
  85. const dCtx = dynamicNoise.getContext('2d');
  86.  
  87. // Mask canvas holds the HUD shape (text) as opaque pixels
  88. const maskCanvas = document.createElement('canvas');
  89. maskCanvas.width = W; maskCanvas.height = H;
  90. const mCtx = maskCanvas.getContext('2d');
  91.  
  92. let hudEnabled = true;
  93.  
  94. function generateStaticNoise(){
  95. // draw a static noise field once (background)
  96. const img = sCtx.createImageData(W,H);
  97. for(let i=0;i<img.data.length;i+=4){
  98. const v = (Math.random()*255)|0;
  99. img.data[i]=img.data[i+1]=img.data[i+2]=v;
  100. img.data[i+3]=255;
  101. }
  102. sCtx.putImageData(img,0,0);
  103. }
  104.  
  105. function resizeMask(){
  106. mCtx.clearRect(0,0,W,H);
  107. // Draw some HUD text and a little minimap box
  108. mCtx.font = 'bold 48px monospace';
  109. mCtx.textAlign = 'center';
  110. mCtx.fillStyle = '#fff';
  111. mCtx.fillText('SECRET', W/2, 80);
  112. // minimap rectangle (we'll use this area as part of the mask too)
  113. mCtx.fillRect(12,12,150,110);
  114. }
  115.  
  116. generateStaticNoise();
  117. resizeMask();
  118.  
  119. // Precompute mask image data
  120. let maskData = mCtx.getImageData(0,0,W,H);
  121. function updateMaskData(){ maskData = mCtx.getImageData(0,0,W,H); }
  122. updateMaskData();
  123.  
  124. // Dynamic noise state (will change each frame inside mask)
  125. function generateDynamicNoiseFrame(tick){
  126. const img = dCtx.createImageData(W,H);
  127. // create motion: vertical scroll of noise + small temporal variation
  128. for(let y=0;y<H;y++){
  129. for(let x=0;x<W;x++){
  130. const idx = (y*W + x)*4;
  131. // Per-pixel value depends on x,y and tick so it moves
  132. const v = ( ((x*37 + y*17 + tick*3) & 255) ^ ((x*y + tick*13)&255) );
  133. img.data[idx]=img.data[idx+1]=img.data[idx+2]=v;
  134. img.data[idx+3]=255;
  135. }
  136. }
  137. dCtx.putImageData(img,0,0);
  138. }
  139.  
  140. // Composite function: show static noise where mask is transparent, dynamic where mask opaque
  141. function compositeNoise(){
  142. // We'll read both images and the mask and draw into main ctx as an overlay
  143. const sImg = sCtx.getImageData(0,0,W,H).data;
  144. const dImg = dCtx.getImageData(0,0,W,H).data;
  145. const m = maskData.data;
  146. const out = ctx.createImageData(W,H);
  147. for(let i=0;i<sImg.length;i+=4){
  148. const alpha = m[i+3]; // mask alpha
  149. if(alpha>16){ // mask present -> show dynamic noise
  150. out.data[i]=dImg[i]; out.data[i+1]=dImg[i+1]; out.data[i+2]=dImg[i+2]; out.data[i+3]=255;
  151. } else {
  152. out.data[i]=sImg[i]; out.data[i+1]=sImg[i+1]; out.data[i+2]=sImg[i+2]; out.data[i+3]=255;
  153. }
  154. }
  155. // draw overlay with some transparency so world still visible under it
  156. ctx.save();
  157. ctx.globalAlpha = 0.85;
  158. ctx.putImageData(out,0,0);
  159. ctx.restore();
  160. }
  161.  
  162. // ----- RAYCASTING -----
  163. function castRays(){
  164. // clear sky/floor
  165. ctx.fillStyle = '#6aa'; ctx.fillRect(0,0,W,H/2); // sky
  166. ctx.fillStyle = '#444'; ctx.fillRect(0,H/2,W,H/2); // floor
  167.  
  168. for(let i=0;i<NUM_RAYS;i++){
  169. const rayScreenPos = (i/NUM_RAYS) - 0.5;
  170. const rayAngle = pa + rayScreenPos * FOV;
  171.  
  172. // unit vector for ray
  173. const rx = Math.cos(rayAngle), ry = Math.sin(rayAngle);
  174.  
  175. // DDA
  176. let mapX = Math.floor(px), mapY = Math.floor(py);
  177. const deltaDistX = Math.abs(1 / rx);
  178. const deltaDistY = Math.abs(1 / ry);
  179.  
  180. let stepX, stepY, sideDistX, sideDistY;
  181. if(rx < 0){ stepX = -1; sideDistX = (px - mapX) * deltaDistX; }
  182. else { stepX = 1; sideDistX = (mapX + 1 - px) * deltaDistX; }
  183. if(ry < 0){ stepY = -1; sideDistY = (py - mapY) * deltaDistY; }
  184. else { stepY = 1; sideDistY = (mapY + 1 - py) * deltaDistY; }
  185.  
  186. let hit = false, side = 0; // side: 0=x, 1=y
  187. let cell;
  188. let perpWallDist = 0;
  189. for(let depth=0; depth<64 && !hit; depth++){
  190. if(sideDistX < sideDistY){
  191. sideDistX += deltaDistX; mapX += stepX; side=0;
  192. } else {
  193. sideDistY += deltaDistY; mapY += stepY; side=1;
  194. }
  195. if(mapX>=0 && mapX<MAPW && mapY>=0 && mapY<MAPH){
  196. cell = map[mapY*MAPW + mapX];
  197. if(cell>0) hit = true;
  198. } else { hit = true; cell=1; }
  199. }
  200.  
  201. if(side===0) perpWallDist = (mapX - px + (1-stepX)/2) / rx;
  202. else perpWallDist = (mapY - py + (1-stepY)/2) / ry;
  203. if(perpWallDist<=0) perpWallDist = 0.0001;
  204.  
  205. // correct fisheye
  206. const correctedDist = perpWallDist * Math.cos(rayAngle-pa);
  207. const lineHeight = (DIST_PROJ_PLANE / correctedDist) | 0;
  208. const drawStart = Math.max(0, -lineHeight/2 + H/2);
  209. const drawEnd = Math.min(H-1, lineHeight/2 + H/2);
  210.  
  211. ctx.fillStyle = wallColor(cell, side);
  212. const columnX = (i * (W / NUM_RAYS)) | 0;
  213. const columnW = Math.ceil(W / NUM_RAYS) + 0.5;
  214. ctx.fillRect(columnX, drawStart, columnW, drawEnd - drawStart);
  215. }
  216. }
  217.  
  218. // ----- SIMPLE MINIMAP -----
  219. function drawMiniMap(){
  220. const size = 140; const sx = 12, sy = 12;
  221. ctx.save();
  222. ctx.globalAlpha = 0.9;
  223. ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(sx-4,sy-4,size+8, size+8);
  224. for(let y=0;y<MAPH;y++){
  225. for(let x=0;x<MAPW;x++){
  226. const v = map[y*MAPW+x];
  227. if(v===0) ctx.fillStyle = '#222'; else ctx.fillStyle = '#ddd';
  228. const cx = sx + (x * (size/MAPW));
  229. const cy = sy + (y * (size/MAPH));
  230. ctx.fillRect(cx, cy, Math.ceil(size/MAPW), Math.ceil(size/MAPH));
  231. }
  232. }
  233. // player
  234. ctx.fillStyle = 'red'; ctx.fillRect(sx + px*(size/MAPW)-2, sy + py*(size/MAPH)-2,4,4);
  235. // facing line
  236. ctx.strokeStyle = 'red'; ctx.beginPath(); ctx.moveTo(sx+px*(size/MAPW), sy+py*(size/MAPH)); ctx.lineTo(sx + (px + Math.cos(pa)*0.8)*(size/MAPW), sy + (py + Math.sin(pa)*0.8)*(size/MAPH)); ctx.stroke();
  237. ctx.restore();
  238. }
  239.  
  240. // ----- GAME LOOP -----
  241. let last = performance.now();
  242. let tick = 0;
  243. function frame(now){
  244. const dt = Math.min(0.05, (now - last)/1000);
  245. last = now;
  246. tick++;
  247.  
  248. // movement
  249. let forward = (keys['w']||keys['arrowup'])?1:0;
  250. let back = (keys['s']||keys['arrowdown'])?1:0;
  251. let left = (keys['a']||keys['arrowleft'])?1:0;
  252. let right = (keys['d']||keys['arrowright'])?1:0;
  253.  
  254. const speed = moveSpeed * dt;
  255. const rot = rotSpeed * dt;
  256.  
  257. // rotate by mouse
  258. pa += (mouseDX * 0.0025);
  259. mouseDX = 0;
  260.  
  261. if(forward) { px += Math.cos(pa)*speed; py += Math.sin(pa)*speed; }
  262. if(back) { px -= Math.cos(pa)*speed; py -= Math.sin(pa)*speed; }
  263. if(left) { pa -= rot; }
  264. if(right) { pa += rot; }
  265.  
  266. // simple collision: keep inside world and not inside walls
  267. if(px<1) px=1; if(px>MAPW-1) px=MAPW-1; if(py<1) py=1; if(py>MAPH-1) py=MAPH-1;
  268. const pcell = map[Math.floor(py)*MAPW + Math.floor(px)]; if(pcell>0){ px = Math.floor(px)+0.5; py = Math.floor(py)+0.5; }
  269.  
  270. // draw world
  271. castRays();
  272.  
  273. // minimap
  274. drawMiniMap();
  275.  
  276. // HUD overlay (unscreenshottable effect)
  277. if(hudEnabled){
  278. generateDynamicNoiseFrame(tick);
  279. compositeNoise();
  280. }
  281.  
  282. requestAnimationFrame(frame);
  283. }
  284. requestAnimationFrame(frame);
  285.  
  286. // Resize handling (optional)
  287. window.addEventListener('resize', ()=>{});
  288.  
  289. // Helpful note: user can toggle HUD with H
  290. </script>
  291. </body>
  292. </html>
  293.  
Advertisement
Add Comment
Please, Sign In to add comment