🎮ArcadeLab

Neon Target Rush

by BlazeGalaxy19
356 lines12.6 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Target Rush</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#080d1c;color:white;font-family:Arial,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh}
#game{width:min(960px,100vw);position:relative}
canvas{width:100%;display:block;background:#101b35;border:2px solid #36d9ff;border-radius:12px;cursor:crosshair;touch-action:none}
#help{text-align:center;color:#b8c9e8;font-size:13px;padding:8px}
</style>
</head>
<body>
<div id="game">
<canvas id="c" width="960" height="560"></canvas>
<div id="help">Move your mouse to aim • Click to fire • Press SPACE to fire • Press P to pause</div>
</div>

<script>
const canvas=document.getElementById("c");
const ctx=canvas.getContext("2d");
const W=960,H=560;

let mode="menu",score=0,level=1,coins=0,best=0;
let shots=0,hits=0,selected=0,ammo=Infinity;
let mouse={x:480,y:280};
let target=null,obstacles=[],particles=[],bullets=[];
let last=0,spawnTimer=0,audio=null,musicTimer=null;
let musicOn=true,soundOn=true;
let leaderboard=JSON.parse(localStorage.getItem("neonBoard")||"[]");
let owned=JSON.parse(localStorage.getItem("neonOwned")||"[0]");
let weapons=[
 {name:"SIDEARM",price:0,damage:1,cooldown:350,color:"#5ce1ff",desc:"Fast starter blaster"},
 {name:"BURST BLASTER",price:80,damage:2,cooldown:500,color:"#ffcf5c",desc:"Powerful energy burst"},
 {name:"PRECISION BLASTER",price:160,damage:3,cooldown:700,color:"#ff75d5",desc:"High power energy shot"}
];
let lastShot=0;

function save(){
 localStorage.setItem("neonBoard",JSON.stringify(leaderboard));
 localStorage.setItem("neonOwned",JSON.stringify(owned));
}
function audioStart(){
 if(!audio) audio=new (window.AudioContext||window.webkitAudioContext)();
 if(audio.state==="suspended")audio.resume();
}
function tone(freq,duration,type="sine",volume=.04){
 if(!soundOn)return;
 audioStart();
 let o=audio.createOscillator(),g=audio.createGain();
 o.type=type;o.frequency.value=freq;
 g.gain.setValueAtTime(volume,audio.currentTime);
 g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+duration);
 o.connect(g);g.connect(audio.destination);
 o.start();o.stop(audio.currentTime+duration);
}
function shootSound(){tone(240,.12,"sawtooth",.035);tone(520,.08,"sine",.025)}
function hitSound(){tone(700,.1,"triangle",.05);tone(950,.16,"sine",.03)}
function music(){
 if(!musicOn)return;
 audioStart();
 let notes=[110,146,174,220,174,146,130,196];
 let i=0;
 if(musicTimer)clearInterval(musicTimer);
 musicTimer=setInterval(()=>{
  if(mode!=="play"||!musicOn)return;
  tone(notes[i%notes.length],.22,"triangle",.012);
  if(i%2===0)tone(55,.18,"sine",.018);
  i++;
 },260);
}
function stopMusic(){if(musicTimer){clearInterval(musicTimer);musicTimer=null}}

function newTarget(){
 let r=22+Math.random()*13;
 target={
  x:Math.random()*(W-180)+90,
  y:100+Math.random()*330,
  r:r,hp:1+Math.floor(level/3),
  vx:(Math.random()<.5?-1:1)*(1.4+level*.22),
  vy:(Math.random()<.5?-1:1)*(0.7+level*.12),
  phase:Math.random()*6
 };
}
function makeObstacles(){
 obstacles=[];
 for(let i=0;i<5+Math.min(level,5);i++){
  obstacles.push({
   x:100+Math.random()*760,
   y:100+Math.random()*350,
   w:45+Math.random()*75,
   h:25+Math.random()*55,
   glow:Math.random()*5
  });
 }
}
function startGame(){
 audioStart();score=0;level=1;shots=0;hits=0;
 particles=[];bullets=[];makeObstacles();newTarget();
 mode="play";last=performance.now();music();
}
function endGame(){
 mode="over";stopMusic();
 leaderboard.push(score);
 leaderboard.sort((a,b)=>b-a);
 leaderboard=leaderboard.slice(0,5);
 best=Math.max(best,score);
 localStorage.setItem("neonBest",best);
 save();
 tone(180,.4,"sawtooth",.05);
}
best=Number(localStorage.getItem("neonBest")||0);

function fire(){
 if(mode!=="play")return;
 let w=weapons[selected],now=performance.now();
 if(now-lastShot<w.cooldown)return;
 lastShot=now;shots++;shootSound();
 let dx=mouse.x-480,dy=mouse.y-510;
 let a=Math.atan2(dy,dx);
 bullets.push({x:480,y:510,vx:Math.cos(a)*13,vy:Math.sin(a)*13,life:55,color:w.color});
}
function burst(x,y,color){
 for(let i=0;i<13;i++){
  let a=Math.random()*Math.PI*2,s=1+Math.random()*5;
  particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:25+Math.random()*20,color});
 }
}
function blocked(x,y){
 return obstacles.some(o=>x>o.x&&x<o.x+o.w&&y>o.y&&y<o.y+o.h);
}
function update(dt){
 if(mode!=="play")return;
 let t=dt/16.67;
 if(!target)newTarget();
 target.x+=target.vx*t;target.y+=target.vy*t;
 target.phase+=.08*t;
 if(target.x<target.r||target.x>W-target.r)target.vx*=-1;
 if(target.y<80||target.y>H-80)target.vy*=-1;

 // Obstacles slowly shift to make each level harder.
 obstacles.forEach(o=>{
  o.x+=Math.sin(performance.now()/900+o.glow)*.35*t;
  if(o.x<20)o.x=20;
  if(o.x+o.w>W-20)o.x=W-o.w-20;
 });

 bullets.forEach(b=>{b.x+=b.vx*t;b.y+=b.vy*t;b.life-=t});
 bullets=bullets.filter(b=>b.life>0&&b.x>0&&b.x<W&&b.y>0&&b.y<H);

 for(let b of bullets){
  if(target&&Math.hypot(b.x-target.x,b.y-target.y)<target.r+5){
   b.life=0;target.hp-=weapons[selected].damage;
   burst(b.x,b.y,weapons[selected].color);hitSound();
   if(target.hp<=0){
    score+=10;coins+=10;hits++;
    if(score>=level*50){level++;makeObstacles();tone(850,.3,"sine",.05)}
    newTarget();
   }
  }else if(blocked(b.x,b.y)){
   b.life=0;burst(b.x,b.y,"#9ba9c9");
  }
 }
 particles.forEach(p=>{p.x+=p.vx*t;p.y+=p.vy*t;p.vx*=.97;p.vy*=.97;p.life-=t});
 particles=particles.filter(p=>p.life>0);
}
function background(){
 let g=ctx.createLinearGradient(0,0,W,H);
 g.addColorStop(0,"#101a3a");g.addColorStop(.5,"#172b48");g.addColorStop(1,"#090f24");
 ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
 ctx.strokeStyle="rgba(60,190,255,.12)";ctx.lineWidth=1;
 for(let x=0;x<W;x+=48){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke()}
 for(let y=0;y<H;y+=48){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke()}
 for(let i=0;i<45;i++){
  let x=(i*173)%W,y=(i*97)%H;
  ctx.fillStyle="rgba(160,220,255,.35)";
  ctx.fillRect(x,y,2,2);
 }
}
function drawTarget(){
 if(!target)return;
 let pulse=Math.sin(target.phase)*4;
 ctx.save();ctx.translate(target.x,target.y);
 ctx.shadowBlur=22;ctx.shadowColor="#ff5b78";
 ctx.fillStyle="#d94d68";
 ctx.beginPath();ctx.arc(0,0,target.r+pulse,0,Math.PI*2);ctx.fill();
 ctx.shadowBlur=0;
 ctx.fillStyle="#29172c";
 ctx.beginPath();ctx.arc(0,0,target.r*.63,0,Math.PI*2);ctx.fill();
 ctx.fillStyle="#ffdf75";
 ctx.beginPath();ctx.arc(-7,-4,4,0,Math.PI*2);ctx.arc(7,-4,4,0,Math.PI*2);ctx.fill();
 ctx.fillStyle="#fff";ctx.fillRect(-8,9,16,4);
 ctx.strokeStyle="#ff9eaa";ctx.lineWidth=3;
 ctx.beginPath();ctx.arc(0,0,target.r+8,0,Math.PI*2);ctx.stroke();
 ctx.restore();
 // Health bar
 ctx.fillStyle="#333";ctx.fillRect(target.x-25,target.y-target.r-19,50,6);
 ctx.fillStyle="#ff647f";ctx.fillRect(target.x-25,target.y-target.r-19,50*Math.max(0,target.hp/(1+Math.floor(level/3))),6);
}
function draw(){
 background();
 // distant city shapes
 for(let i=0;i<13;i++){
  let x=i*78,h=70+(i*47)%100;
  ctx.fillStyle="#101b35";ctx.fillRect(x,H-h,60,h);
  ctx.fillStyle="rgba(66,205,255,.3)";
  for(let yy=H-h+12;yy<H-10;yy+=19)ctx.fillRect(x+12,yy,5,5);
 }
 // Obstacles
 obstacles.forEach(o=>{
  ctx.fillStyle="#263d5d";ctx.strokeStyle="#6c9ab9";ctx.lineWidth=2;
  ctx.fillRect(o.x,o.y,o.w,o.h);ctx.strokeRect(o.x,o.y,o.w,o.h);
  ctx.fillStyle="rgba(67,213,255,.2)";ctx.fillRect(o.x+5,o.y+5,o.w-10,5);
 });
 drawTarget();
 // Energy bolts
 bullets.forEach(b=>{
  ctx.shadowBlur=15;ctx.shadowColor=b.color;ctx.fillStyle=b.color;
  ctx.beginPath();ctx.arc(b.x,b.y,6,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;
 });
 particles.forEach(p=>{
  ctx.globalAlpha=Math.max(0,p.life/40);ctx.fillStyle=p.color;
  ctx.fillRect(p.x,p.y,5,5);ctx.globalAlpha=1;
 });
 // Player blaster at bottom
 ctx.save();ctx.translate(480,510);
 let a=Math.atan2(mouse.y-510,mouse.x-480);
 ctx.rotate(a);
 ctx.fillStyle=weapons[selected].color;
 ctx.shadowBlur=15;ctx.shadowColor=weapons[selected].color;
 ctx.fillRect(-4,-9,42,18);ctx.fillRect(25,-5,15,10);
 ctx.shadowBlur=0;ctx.restore();

 ctx.fillStyle="white";ctx.font="bold 20px Arial";
 ctx.fillText("SCORE: "+score,22,34);
 ctx.fillText("LEVEL: "+level,22,62);
 ctx.fillText("COINS: "+coins,22,90);
 ctx.textAlign="right";ctx.fillText("BEST: "+best,W-22,34);ctx.textAlign="left";
 if(mode==="play"){
  ctx.fillStyle="#b8d9f5";ctx.font="14px Arial";
  ctx.fillText("BLASTER: "+weapons[selected].name,22,H-20);
 }
 if(mode!=="play")drawMenu();
}
function panel(){
 ctx.fillStyle="rgba(5,10,25,.88)";
 ctx.fillRect(190,80,580,400);
 ctx.strokeStyle="#39dfff";ctx.lineWidth=2;
 ctx.strokeRect(190,80,580,400);
}
function drawMenu(){
 panel();ctx.textAlign="center";
 if(mode==="menu"){
  ctx.fillStyle="#5ce1ff";ctx.font="bold 42px Arial";ctx.fillText("NEON TARGET RUSH",480,155);
  ctx.fillStyle="white";ctx.font="20px Arial";ctx.fillText("Aim • Dodge obstacles • Hit the moving bot!",480,205);
  button("START GAME",350,245,260,55);
  button("WEAPON SHOP",350,315,260,48);
  button("LEADERBOARD",350,378,260,48);
 }else if(mode==="shop"){
  ctx.fillStyle="#5ce1ff";ctx.font="bold 32px Arial";ctx.fillText("BLASTER SHOP",480,125);
  ctx.fillStyle="white";ctx.font="17px Arial";ctx.fillText("Coins: "+coins,480,158);
  weapons.forEach((w,i)=>{
   let y=190+i*65;
   ctx.fillStyle=i===selected?"#284c6b":"#182943";
   ctx.fillRect(245,y,470,54);
   ctx.fillStyle=w.color;ctx.font="bold 18px Arial";ctx.fillText(w.name,365,y+23);
   ctx.fillStyle="white";ctx.font="14px Arial";
   ctx.fillText(owned.includes(i)?"OWNED":w.price+" COINS",590,y+23);
  });
  button("BACK",390,405,180,45);
 }else if(mode==="board"){
  ctx.fillStyle="#5ce1ff";ctx.font="bold 32px Arial";ctx.fillText("LOCAL LEADERBOARD",480,135);
  ctx.fillStyle="white";ctx.font="20px Arial";
  for(let i=0;i<5;i++)ctx.fillText((i+1)+".     "+(leaderboard[i]??"---"),480,195+i*42);
  button("BACK",390,405,180,45);
 }else if(mode==="over"){
  ctx.fillStyle="#ff7894";ctx.font="bold 42px Arial";ctx.fillText("ROUND OVER!",480,160);
  ctx.fillStyle="white";ctx.font="22px Arial";ctx.fillText("Your score: "+score,480,215);
  ctx.fillText("Targets hit: "+hits,480,250);
  button("PLAY AGAIN",350,290,260,52);
  button("MAIN MENU",350,355,260,48);
 }else if(mode==="pause"){
  ctx.fillStyle="#5ce1ff";ctx.font="bold 42px Arial";ctx.fillText("PAUSED",480,205);
  button("RESUME",350,265,260,55);
  button("MAIN MENU",350,340,260,48);
 }
 ctx.textAlign="left";
}
function button(label,x,y,w,h){
 ctx.fillStyle="#174c6c";ctx.fillRect(x,y,w,h);
 ctx.strokeStyle="#5ce1ff";ctx.lineWidth=2;ctx.strokeRect(x,y,w,h);
 ctx.fillStyle="white";ctx.font="bold 19px Arial";ctx.textAlign="center";
 ctx.fillText(label,x+w/2,y+h/2+7);ctx.textAlign="left";
}
function clickMenu(x,y){
 if(mode==="menu"){
  if(x>350&&x<610&&y>245&&y<300)startGame();
  else if(x>350&&x<610&&y>315&&y<363)mode="shop";
  else if(x>350&&x<610&&y>378&&y<426)mode="board";
 }else if(mode==="shop"){
  if(y>190&&y<244){selectWeapon(0)}
  if(y>255&&y<309){selectWeapon(1)}
  if(y>320&&y<374){selectWeapon(2)}
  if(y>405&&y<450)mode="menu";
 }else if(mode==="board"){
  if(y>405&&y<450)mode="menu";
 }else if(mode==="over"){
  if(y>290&&y<342)startGame();
  if(y>355&&y<403)mode="menu";
 }else if(mode==="pause"){
  if(y>265&&y<320){mode="play";music()}
  if(y>340&&y<390){mode="menu";stopMusic()}
 }
}
function selectWeapon(i){
 if(owned.includes(i)){selected=i;return}
 if(coins>=weapons[i].price){
  coins-=weapons[i].price;owned.push(i);selected=i;save();
  tone(800,.2,"sine",.05);
 }else tone(120,.2,"square",.04);
}
function position(e){
 let r=canvas.getBoundingClientRect();
 return {x:(e.clientX-r.left)*W/r.width,y:(e.clientY-r.top)*H/r.height};
}
canvas.addEventListener("pointermove",e=>{mouse=position(e)});
canvas.addEventListener("pointerdown",e=>{
 e.preventDefault();audioStart();
 let p=position(e);mouse=p;
 if(mode!=="play"){clickMenu(p.x,p.y);return}
 fire();
});
window.addEventListener("keydown",e=>{
 if(e.code==="Space"){
  e.preventDefault();
  if(mode==="play")fire();
  else if(mode==="menu")startGame();
  else if(mode==="over")startGame();
  else if(mode==="pause"){mode="play";music()}
 }
 if(e.key.toLowerCase()==="p"&&mode==="play"){mode="pause";stopMusic()}
 if(e.key==="Escape"&&mode==="play"){mode="pause";stopMusic()}
});
function loop(now){
 let dt=Math.min(40,now-last||16);last=now;
 update(dt);draw();
 requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>

Game Source: Neon Target Rush

Creator: BlazeGalaxy19

Libraries: none

Complexity: complex (356 lines, 12.6 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: neon-target-rush-blazegalaxy19-mw54vhc6" to link back to the original. Then publish at arcadelab.ai/publish.