🎮ArcadeLab

迷你三角洲‑V5

by SuperFlare63
521 lines24.9 KB
▶ Play
<!DOCTYPE html>
 <html lang="zh‑CN">
 <head>
 <meta charset="UTF‑8">
 <meta name="viewport" content="width=device‑width, initial‑scale=1.0, maximum‑scale=1, user‑scalable=no">
 <title>迷你三角洲‑V5</title>
 <style>
 *{margin:0;padding:0;box‑sizing:border‑box}
 body{background:#0b0b0b;overflow:hidden;font‑family:sans‑webkit,sans‑serif;color:white}
 canvas{display:block;width:100vw;height:100vh;touch‑action:none}
 #ui{position:fixed;inset:0;pointer‑events:none}
 .btn{pointer‑events:auto;background:rgba(30,120,220,.5);border:2px solid #7af;color:#fff;font‑size:14px;border‑radius:10px;padding:8px}
 #joystick{position:absolute;left:30px;bottom:40px;width:120px;height:120px;border‑radius:50%;background:rgba(255,255,255,.15);border:2px solid rgba(255,255,255,.3);pointer‑events:auto}
 #stick{width:50px;height:50px;border‑radius:50%;background:rgba(80,180,255,.6);position:absolute;left:35px;top:35px}
 #fireBtn{position:absolute;right:30px;bottom:70px;width:90px;height:90px;border‑radius:50%;background:rgba(220,40,40,.5);border:2px solid #f66;color:white;font‑size:18px;pointer‑events:auto}
 #switchGun{position:absolute;right:140px;bottom:90px;width:70px;height:70px;border‑radius:12px;background:rgba(40,120,220,.5);border:2px solid #77bbff;color:#fff;font‑size:13px;pointer‑events:auto}
 #healBtn{position:absolute;right:140px;bottom:175px;width:70px;height:70px;border‑radius:12px;background:rgba(255,255,255,.35);border:2px solid #fff;color:#fff;font‑size:13px;pointer‑events:auto}
 #minimap{position:absolute;top:15px;right:15px;width:130px;height:130px;border:2px solid rgba(255,255,255,.4);background:rgba(0,0,0,.4);pointer‑events:auto}
 #hp{position:absolute;top:15px;left:15px;font‑size:18px;text‑shadow:1px 1px #000}
 #armor{position:absolute;top:38px;left:15px;font‑size:17px;text‑shadow:1px 1px #000}
 #gunName{position:absolute;top:60px;left:15px;font‑size:16px;text‑shadow:1px 1px #000}
 #kill{position:absolute;top:82px;left:15px;font‑size:15px;text‑shadow:1px 1px #000}
 #extractText{position:absolute;top:104px;left:15px;font‑size:15px;color:#7f7;text‑shadow:1px 1px #000}
 #medText{position:absolute;top:126px;left:15px;font‑size:15px;color:#f9c;text‑shadow:1px 1px #000}
 #missionText{position:absolute;top:148px;left:15px;font‑size:14px;color:#ffd;text‑shadow:1px 1px #000;max‑width:220px}
 #timerText{position:absolute;top:170px;left:15px;font‑size:15px;color:#f55;text‑shadow:1px 1px #000}
 #menu{position:fixed;inset:0;background:#131822;display:flex;flex‑direction:column;justify‑content:flex‑end;padding:30px;pointer‑events:none}
 #menu button{pointer‑events:auto;width:130px;height:60px;font‑size:16px;margin:8px;border‑radius:10px;border:none;color:white}
 #btnStore{background:#287}
 #btnStash{background:#269}
 #btnStart{background:#c33;margin‑left:auto}
 #overlay{position:fixed;inset:0;background:rgba(0,0,0,.85);display:none;padding:16px;overflow‑y:auto;pointer‑events:auto}
 #overlay h2{margin‑bottom:12px}
 .item{padding:10px;border‑bottom:1px solid #444;display:flex;justify‑content:space‑between;align‑items:center}
 .hidden{display:none !important}
 </style>
 </head>
 <body>
 <canvas id="game"></canvas>
 <canvas id="minimap"></canvas>
 <div id="ui">
 <div id="joystick"><div id="stick"></div></div>
 <button id="fireBtn">开火</button>
 <button id="switchGun">换枪</button>
 <button id="healBtn">药品</button>
 <div id="hp">生命:100</div>
 <div id="armor">护甲:0</div>
 <div id="gunName">武器:步枪</div>
 <div id="kill">击杀:0</div>
 <div id="extractText">等待撤离开启</div>
 <div id="medText">药品:0</div>
 <div id="missionText">任务:无</div>
 <div id="timerText">撤离倒计时:‑‑</div>
 </div>
 <div id="menu">
 <button id="btnStash">仓库</button>
 <button id="btnStore">商店</button>
 <button id="btnStart">开始行动</button>
 </div>
 <div id="overlay">
 <h2 id="ovTitle">仓库</h2>
 <div id="ovList"></div>
 <button onclick="closeOverlay()" style="margin‑top:16px;padding:10px;width:100%">关闭</button>
 </div>
 <script>
 const canvas=document.getElementById("game");
 const ctx=canvas.getContext("2d");
 const mm=document.getElementById("minimap");
 const mmCtx=mm.getContext("2d");
 let W,H;
 function resize(){W=canvas.width=innerWidth;H=canvas.height=innerHeight;mm.width=130;mm.height=130}
 resize();addEventListener("resize",resize);
 // =====全局数据=====
 const WORLD={w:3200,h:3200};
 let camera={x:0,y:0};
 let inGame=false;
 let minimapZoom=false;
 //仓库存档
 let save=JSON.parse(localStorage.getItem("deltaSave"))||{
     money:800,
     stashWeapons:[{id:"rifle",mods:{muzzle:null,mag:null,sight:null}}],
     stashArmors:[],
     meds:3,
     stashMods:{muzzle:0,mag:0,sight:0}
 };
 function saveData(){localStorage.setItem("deltaSave",JSON.stringify(save))}
 //武器定义,新增霰弹枪、机枪
 const gunDef={
     rifle:{name:"步枪",damage:12,speed:9,rate:12,spread:0.06,price:100,bullets:1},
     smg:{name:"冲锋枪",damage:7,speed:11,rate:5,spread:0.11,price:120,bullets:1},
     sniper:{name:"狙击枪",damage:45,speed:16,rate:45,spread:0.015,price:260,bullets:1},
     shotgun:{name:"霰弹枪",damage:9,speed:8,rate:32,spread:0.22,price:220,bullets:6},
     lmg:{name:"轻机枪",damage:10,speed:9,rate:7,spread:0.09,price:300,bullets:1}
 };
 //配件
 const modDef={
     muzzle:{name:"消焰器",spread:‑0.02,price:80},
     mag:{name:"扩容弹匣",rate:‑2,price:70},
     sight:{name:"高倍瞄具",spread:‑0.015,price:90}
 };
 //敌人类型
 const enemyTypes={
     normal:{hp:30,speed:1.2,range:340,dmg:8,color:"#882222"},
     rush:{hp:45,speed:2.4,range:400,dmg:14,color:"#dd5500"},
     sniper:{hp:22,speed:0.7,range:520,dmg:22,color:"#6622aa"},
     boss:{hp:400,speed:1.0,range:460,dmg:18,color:"#aa1111",r:28}
 };
 //任务库
 const missionPool=[
     {desc:"击杀10名敌人",target:10,type:"kill"},
     {desc:"击杀20名敌人",target:20,type:"kill"},
     {desc:"成功撤离一次",target:1,type:"extract"}
 ];
 let currentMission=null,missionProgress=0;
 //音效
 let audioCtx;
 function initAudio(){if(!audioCtx)audioCtx=new (window.AudioContext||window.webkitAudioContext)()}
 function playGunSound(){
     if(!audioCtx)return;const o=audioCtx.createOscillator(),g=audioCtx.createGain();
     o.type="square";o.frequency.setValueAtTime(220,audioCtx.currentTime);
     o.frequency.exponentialRampToValueAtTime(60,audioCtx.currentTime+0.08);
     g.gain.setValueAtTime(0.18,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.09);
     o.connect(g);g.connect(audioCtx.destination);o.start();o.stop(audioCtx.currentTime+0.09);
 }
 function playFootstep(){
     if(!audioCtx)return;const o=audioCtx.createOscillator(),g=audioCtx.createGain();
     o.type="sine";o.frequency.setValueAtTime(90,audioCtx.currentTime);
     g.gain.setValueAtTime(0.06,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.07);
     o.connect(g);g.connect(audioCtx.destination);o.start();o.stop(audioCtx.currentTime+0.07);
 }
 let player,bullets=[],enemies=[],enemyBullets=[],airDrops=[],cover=[],groundMods=[];
 let extractPoint,extractActive=false,extractTimer=0,extractCountdown=0,extractDuration=60*20;
 let currentGunIdx=0,fireCooldown=0,recoil=0;
 let spawnTimer=0,stepTimer=0;
 //摇杆
 const joy=document.getElementById("joystick");
 const stick=document.getElementById("stick");
 let jx=0,jy=0;
 joy.addEventListener("touchstart",e=>{initAudio();e.preventDefault()});
 joy.addEventListener("touchmove",e=>{
     const t=e.touches[0];const r=joy.getBoundingClientRect();
     let dx=t.clientX‑(r.left+r.width/2),dy=t.clientY‑(r.top+r.height/2);
     const dist=Math.hypot(dx,dy),max=40;
     if(dist>max){dx=dx/dist*max;dy=dy/dist*max}
     stick.style.left=(35+dx)+"px";stick.style.top=(35+dy)+"px";
     jx=dx/max;jy=dy/max;e.preventDefault();
 });
 joy.addEventListener("touchend",()=>{jx=0;jy=0;stick.style.left="35px";stick.style.top="35px"});
 const fireBtn=document.getElementById("fireBtn");let shooting=false;
 fireBtn.addEventListener("touchstart",e=>{initAudio();shooting=true;e.preventDefault()});
 fireBtn.addEventListener("touchend",()=>shooting=false);
 document.getElementById("switchGun").addEventListener("touchstart",e=>{
     if(!inGame)return;currentGunIdx=(currentGunIdx+1)%save.stashWeapons.length;e.preventDefault();
 });
 document.getElementById("healBtn").addEventListener("touchstart",e=>{
     if(!inGame||player.meds<=0||player.hp>=100)return;
     player.meds‑‑;player.hp=Math.min(100,player.hp+35);e.preventDefault();
 });
 //小地图点击放大
 mm.addEventListener("touchstart",()=>{minimapZoom=!minimapZoom});
 //主菜单按钮
 document.getElementById("btnStart").onclick=startGame;
 document.getElementById("btnStore").onclick=openStore;
 document.getElementById("btnStash").onclick=openStash;
 const overlay=document.getElementById("overlay"),ovTitle=document.getElementById("ovTitle"),ovList=document.getElementById("ovList");
 function closeOverlay(){overlay.style.display="none"}
 function openStash(){
     ovTitle.innerText="仓库";ovList.innerHTML="";
     ovList.innerHTML+="<div class='item'><span>金币:"+save.money+"</span></div>";
     ovList.innerHTML+="<div class='item'><span>药品:"+save.meds+"</span></div>";
     ovList.innerHTML+="<div class='item'><span>消焰器:"+save.stashMods.muzzle+"</span></div>";
     ovList.innerHTML+="<div class='item'><span>扩容弹匣:"+save.stashMods.mag+"</span></div>";
     ovList.innerHTML+="<div class='item'><span>高倍瞄具:"+save.stashMods.sight+"</span></div>";
     save.stashWeapons.forEach((w,i)=>{
         const d=gunDef[w.id];
         ovList.innerHTML+=`<div class="item"><span>武器‑${d.name} 消焰:${w.mods.muzzle?'是':'否'} 弹匣:${w.mods.mag?'是':'否'} 瞄具:${w.mods.sight?'是':'否'}</span><button onclick="addMod(${i})">加装配件</button></div>`;
     });
     overlay.style.display="block";
 }
 function addMod(idx){
     const w=save.stashWeapons[idx];
     let added=false;
     for(let k in modDef){
         if(!w.mods[k] && save.stashMods[k]>0){
             save.stashMods[k]‑=1;
             w.mods[k]=true;
             added=true;
             break;
         }
     }
     if(!added){
         for(let k in modDef){
             if(!w.mods[k]){
                 if(save.money>=modDef[k].price){
                     save.money‑=modDef[k].price;w.mods[k]=true;saveData();openStash();return;
                 }
             }
         }
         alert("配件已满或者金币、库存配件不足");
         return;
     }
     saveData();openStash();
 }
 function openStore(){
     ovTitle.innerText="商店";ovList.innerHTML="";
     ovList.innerHTML+="<div class='item'><span>金币:"+save.money+"</span></div>";
     for(let gid in gunDef){
         const g=gunDef[gid];
         ovList.innerHTML+=`<div class="item"><span>${g.name} ${g.price}金币</span><button onclick="buyGun('${gid}')">购买</button></div>`;
     }
     ovList.innerHTML+=`<div class="item"><span>药品x1‑30金币</span><button onclick="buyMed()">购买</button></div>`;
     ovList.innerHTML+=`<div class="item"><span>护甲‑80金币</span><button onclick="buyArmor()">购买</button></div>`;
     ovList.innerHTML+=`<div class="item"><span>消焰器‑80金币</span><button onclick="buyMod('muzzle')">购买</button></div>`;
     ovList.innerHTML+=`<div class="item"><span>扩容弹匣‑70金币</span><button onclick="buyMod('mag')">购买</button></div>`;
     ovList.innerHTML+=`<div class="item"><span>高倍瞄具‑90金币</span><button onclick="buyMod('sight')">购买</button></div>`;
     overlay.style.display="block";
 }
 window.buyGun=function(id){
     if(save.money>=gunDef[id].price){
         save.money‑=gunDef[id].price;
         save.stashWeapons.push({id:id,mods:{muzzle:null,mag:null,sight:null}});
         saveData();openStore();
     }else alert("金币不足");
 };
 window.buyMed=function(){
     if(save.money>=30){save.money‑=30;save.meds++;saveData();openStore()}else alert("金币不足")
 };
 window.buyArmor=function(){
     if(save.money>=80){save.money‑=80;save.stashArmors.push(50);saveData();openStore()}else alert("金币不足")
 };
 window.buyMod=function(k){
     if(save.money>=modDef[k].price){
         save.money‑=modDef[k].price;
         save.stashMods[k]+=1;
         saveData();openStore();
     }else alert("金币不足");
 };
 //生成掩体
 function buildCovers(){
     cover=[];
     for(let i=0;i<35;i++){
         const x=150+Math.random()*(WORLD.w‑300),y=150+Math.random()*(WORLD.h‑300);
         const w=30+Math.random()*70,h=30+Math.random()*70;
         cover.push({x,y,w,h,type:Math.random()<0.5?"rock":"building"});
     }
 }
 //地面配件生成
 function spawnGroundMods(){
     groundMods=[];
     const keys=["muzzle","mag","sight"];
     for(let i=0;i<18;i++){
         const k=keys[Math.floor(Math.random()*keys.length)];
         groundMods.push({
             x:120+Math.random()*(WORLD.w‑240),
             y:120+Math.random()*(WORLD.h‑240),
             r:14,
             kind:k
         });
     }
 }
 function startGame(){
     document.getElementById("menu").classList.add("hidden");
     inGame=true;bullets=[];enemies=[];enemyBullets=[];airDrops=[];
     extractTimer=0;extractActive=false;extractCountdown=0;
     player={x:WORLD.w/2,y:WORLD.h/2,r:16,speed:3,hp:100,armor:save.stashArmors.pop()||0,kill:0,angle:0,meds:save.meds};
     save.meds=0;saveData();
     extractPoint={x:WORLD.w*0.8,y:WORLD.h*0.2,r:60};
     buildCovers();
     spawnGroundMods();
     currentGunIdx=0;fireCooldown=0;recoil=0;
     //随机本局任务
     currentMission=missionPool[Math.floor(Math.random()*missionPool.length)];
     missionProgress=0;
 }
 function endGame(victory){
     inGame=false;
     save.meds=player.meds;
     save.stashArmors.push(player.armor);
     save.money+=player.kill*15;
     //任务奖励
     if(currentMission){
         let done=false;
         if(currentMission.type==="kill" && missionProgress>=currentMission.target) done=true;
         if(currentMission.type==="extract" && victory) done=true;
         if(done) save.money+=250;
     }
     saveData();
     document.getElementById("menu").classList.remove("hidden");
 }
 function spawnEnemy(){
     const side=Math.floor(Math.random()*4);let ex,ey;
     if(side===0){ex=Math.random()*WORLD.w;ey=‑20}
     else if(side===1){ex=WORLD.w+20;ey=Math.random()*WORLD.h}
     else if(side===2){ex=Math.random()*WORLD.w;ey=WORLD.h+20}
     else {ex=‑20;ey=Math.random()*WORLD.h}
     let type="normal";
     const r=Math.random();
     if(player.kill>=12&&r<0.06)type="boss";
     else if(r<0.24)type="rush";
     else if(r<0.42)type="sniper";
     const t=enemyTypes[type];
     enemies.push({x:ex,y:ey,r:t.r||15,hp:t.hp,speed:t.speed,shootCd:Math.random()*60,type:type,range:t.range,dmg:t.dmg});
 }
 function spawnAirdrop(){
     const r=Math.random();let loot;
     if(r<0.35)loot="armor";else if(r<0.65)loot="med";else loot="gun";
     airDrops.push({x:80+Math.random()*(WORLD.w‑160),y:80+Math.random()*(WORLD.h‑160),r:22,lootType:loot});
 }
 function rectCircleHit(rect,cx,cy,cr){
     const nx=Math.max(rect.x,Math.min(cx,rect.x+rect.w));
     const ny=Math.max(rect.y,Math.min(cy,rect.y+rect.h));
     return (cx‑nx)*(cx‑nx)+(cy‑ny)*(cy‑ny)<cr*cr;
 }
 function loop(){
     requestAnimationFrame(loop);
     ctx.fillStyle="#000";ctx.fillRect(0,0,W,H);
     if(!inGame){return}
     camera.x=player.x‑W/2;camera.y=player.y‑H/2;
     //世界地面
     ctx.save();
     ctx.translate(‑camera.x,‑camera.y);
     ctx.fillStyle="#3a4a32";ctx.fillRect(0,0,WORLD.w,WORLD.h);
     ctx.fillStyle="#2e3d27";
     for(let i=0;i<400;i++){ctx.fillRect((i*97)%WORLD.w,(i*53)%WORLD.h,3,3)}
     //围墙
     ctx.strokeStyle="#553322";ctx.lineWidth=16;ctx.strokeRect(0,0,WORLD.w,WORLD.h);
     //掩体
     for(let c of cover){
         ctx.fillStyle=c.type==="rock"?"#777":"#543";
         ctx.fillRect(c.x,c.y,c.w,c.h);
     }
     //地面配件
     for(let i=groundMods.length‑1;i>=0;i‑‑){
         const m=groundMods[i];
         ctx.fillStyle=m.kind==="muzzle"?"#ff8":m.kind==="mag"?"#8ff":"#f8f";
         ctx.beginPath();ctx.arc(m.x,m.y,m.r,0,Math.PI*2);ctx.fill();
         if(Math.hypot(player.x‑m.x,player.y‑m.y)<player.r+m.r){
             save.stashMods[m.kind]+=1;
             saveData();
             groundMods.splice(i,1);
         }
     }
     //玩家移动
     const vx=jx*player.speed,vy=jy*player.speed;
     player.x+=vx;player.y+=vy;
     player.x=Math.max(player.r,Math.min(WORLD.w‑player.r,player.x));
     player.y=Math.max(player.r,Math.min(WORLD.h‑player.r,player.y));
     if(Math.abs(vx)+Math.abs(vy)>0.1){stepTimer++;if(stepTimer>22){playFootstep();stepTimer=0}}
     //瞄准最近敌人
     let near=null,nd=9999;
     for(let e of enemies){const d=Math.hypot(e.x‑player.x,e.y‑player.y);if(d<nd){nd=d;near=e}}
     if(near)player.angle=Math.atan2(near.y‑player.y,near.x‑player.x);
     //后坐力衰减
     recoil*=0.82;
     fireCooldown‑‑;
     const weapon=save.stashWeapons[currentGunIdx];
     const base=gunDef[weapon.id];
     let spread=base.spread,reloadRate=base.rate;
     if(weapon.mods.muzzle)spread+=modDef.muzzle.spread;
     if(weapon.mods.sight)spread+=modDef.sight.spread;
     if(weapon.mods.mag)reloadRate+=modDef.mag.rate;
     if(shooting&&fireCooldown<=0){
         for(let b=0;b<base.bullets;b++){
             const spray=player.angle+(Math.random()‑0.5)*spread+recoil*0.025;
             bullets.push({
                 x:player.x+Math.cos(spray)*18,y:player.y+Math.sin(spray)*18,
                 vx:Math.cos(spray)*base.speed,vy:Math.sin(spray)*base.speed,
                 damage:base.damage,life:180
             });
         }
         fireCooldown=reloadRate;recoil+=1;playGunSound();
     }
     //玩家子弹
     for(let i=bullets.length‑1;i>=0;i‑‑){
         const b=bullets[i];b.x+=b.vx;b.y+=b.vy;b.life‑‑;
         let dead=false;
         if(b.life<=0||b.x<0||b.y<0||b.x>WORLD.w||b.y>WORLD.h)dead=true;
         for(let c of cover){if(rectCircleHit(c,b.x,b.y,3))dead=true;break}
         if(dead){bullets.splice(i,1);continue}
         ctx.fillStyle="#ffdd33";ctx.beginPath();ctx.arc(b.x,b.y,4,0,Math.PI*2);ctx.fill();
     }
     //敌人
     for(let i=enemies.length‑1;i>=0;i‑‑){
         const e=enemies[i];
         const dx=player.x‑e.x,dy=player.y‑e.y,d=Math.hypot(dx,dy);
         if(e.type==="rush"||e.type==="boss"){e.x+=(dx/d)*e.speed;e.y+=(dy/d)*e.speed}
         else{if(d>130){e.x+=(dx/d)*e.speed;e.y+=(dy/d)*e.speed}}
         e.shootCd‑‑;
         if(e.shootCd<=0&&d<e.range){
             enemyBullets.push({x:e.x,y:e.y,vx:(dx/d)*5,vy:(dy/d)*5,life:200,dmg:e.dmg});
             e.shootCd=(e.type==="sniper")?140:90;
         }
         for(let j=bullets.length‑1;j>=0;j‑‑){
             const b=bullets[j];
             if(Math.hypot(b.x‑e.x,b.y‑e.y)<e.r){e.hp‑=b.damage;bullets.splice(j,1);break}
         }
         if(e.hp<=0){
             enemies.splice(i,1);player.kill++;
             missionProgress=player.kill;
             if(Math.random()<0.18)player.meds++;
             continue;
         }
         ctx.fillStyle=enemyTypes[e.type].color;
         ctx.beginPath();ctx.arc(e.x,e.y,e.r,0,Math.PI*2);ctx.fill();
     }
     //敌方子弹
     for(let i=enemyBullets.length‑1;i>=0;i‑‑){
         const b=enemyBullets[i];b.x+=b.vx;b.y+=b.vy;b.life‑‑;
         let dead=false;
         if(b.life<=0||b.x<0||b.y<0||b.x>WORLD.w||b.y>WORLD.h)dead=true;
         for(let c of cover){if(rectCircleHit(c,b.x,b.y,5)){dead=true;break}}
         if(dead){enemyBullets.splice(i,1);continue}
         if(Math.hypot(b.x‑player.x,b.y‑player.y)<player.r){
             let dmg=b.dmg;
             if(player.armor>0){const absorb=Math.min(player.armor,dmg);player.armor‑=absorb;dmg‑=absorb}
             player.hp‑=dmg;enemyBullets.splice(i,1);continue;
         }
         ctx.fillStyle="#ff5555";ctx.beginPath();ctx.arc(b.x,b.y,5,0,Math.PI*2);ctx.fill();
     }
     //空投
     for(let i=airDrops.length‑1;i>=0;i‑‑){
         const a=airDrops[i];
         ctx.fillStyle="#cc8822";ctx.fillRect(a.x‑a.r,a.y‑a.r,a.r*2,a.r*2);
         ctx.strokeStyle="#fff";ctx.lineWidth=2;ctx.strokeRect(a.x‑a.r,a.y‑a.r,a.r*2,a.r*2);
         if(Math.hypot(player.x‑a.x,player.y‑a.y)<a.r+player.r){
             if(a.lootType==="armor")player.armor=Math.min(100,player.armor+50);
             else if(a.lootType==="med")player.meds+=2;
             else {
                 const keys=Object.keys(gunDef);
                 const rid=keys[Math.floor(Math.random()*keys.length)];
                 save.stashWeapons.push({id:rid,mods:{muzzle:null,mag:null,sight:null}});
                 saveData();
             }
             airDrops.splice(i,1);
         }
     }
     //撤离
     extractTimer++;
     if(extractTimer>1200&&!extractActive){
         extractActive=true;
         extractCountdown=extractDuration;
     }
     if(extractActive){
         extractCountdown‑‑;
         ctx.strokeStyle="#33ff55";ctx.lineWidth=3;
         ctx.beginPath();ctx.arc(extractPoint.x,extractPoint.y,extractPoint.r,0,Math.PI*2);ctx.stroke();
         ctx.fillStyle="#33ff55";ctx.font="18px sans‑serif";ctx.textAlign="center";
         ctx.fillText("撤离点",extractPoint.x,extractPoint.y);
         if(Math.hypot(player.x‑extractPoint.x,player.y‑extractPoint.y)<extractPoint.r){
             ctx.fillStyle="rgba(0,0,0,.7)";ctx.fillRect(camera.x,camera.y,W,H);
             ctx.fillStyle="#55ff77";ctx.font="38px sans‑serif";ctx.textAlign="center";
             ctx.fillText("成功撤离!任务胜利",camera.x+W/2,camera.y+H/2‑25);
             ctx.fillStyle="#fff";ctx.font="24px sans‑serif";
             ctx.fillText("总击杀:"+player.kill,camera.x+W/2,camera.y+H/2+20);
             ctx.restore();
             setTimeout(()=>endGame(true),1800);
             return;
         }
         //倒计时归零撤离关闭
         if(extractCountdown<=0){
             ctx.fillStyle="rgba(0,0,0,.7)";ctx.fillRect(camera.x,camera.y,W,H);
             ctx.fillStyle="#ff5555";ctx.font="36px sans‑serif";ctx.textAlign="center";
             ctx.fillText("撤离时间结束,撤离失败",camera.x+W/2,camera.y+H/2);
             ctx.restore();
             setTimeout(()=>endGame(false),1800);
             return;
         }
     }
     //绘制玩家
     ctx.save();ctx.translate(player.x,player.y);ctx.rotate(player.angle);
     ctx.fillStyle="#3377cc";ctx.beginPath();ctx.arc(0,0,player.r,0,Math.PI*2);ctx.fill();
     ctx.fillStyle="#222";ctx.fillRect(8,‑3,22,6);ctx.restore();
     ctx.restore();
     //生成敌人空投
     spawnTimer++;
     if(spawnTimer>120&&enemies.length<16){spawnEnemy();spawnTimer=0}
     if(player.kill>0&&player.kill%8===0&&airDrops.length<2)spawnAirdrop();
     //小地图
     let mmS=minimapZoom?260:130;mm.width=mmS;mm.height=mmS;
     mmCtx.clearRect(0,0,mmS,mmS);
     mmCtx.fillStyle="#2d3b26";mmCtx.fillRect(0,0,mmS,mmS);
     const sx=mmS/WORLD.w,sy=mmS/WORLD.h;
     if(extractActive){mmCtx.fillStyle="#3f5";mmCtx.beginPath();mmCtx.arc(extractPoint.x*sx,extractPoint.y*sy,5,0,Math.PI*2);mmCtx.fill()}
     mmCtx.fillStyle="#c82";for(let a of airDrops)mmCtx.fillRect(a.x*sx‑2,a.y*sy‑2,4,4);
     for(let m of groundMods){
         mmCtx.fillStyle="#ff00ff";
         mmCtx.fillRect(m.x*sx‑1,m.y*sy‑1,3,3);
     }
     for(let e of enemies){
         if(e.type==="rush")mmCtx.fillStyle="#f72";else if(e.type==="sniper")mmCtx.fillStyle="#a4f";else if(e.type==="boss")mmCtx.fillStyle="#f11";else mmCtx.fillStyle="#f33";
         mmCtx.fillRect(e.x*sx‑2,e.y*sy‑2,4,4);
     }
     mmCtx.fillStyle="#4af";mmCtx.beginPath();mmCtx.arc(player.x*sx,player.y*sy,4,0,Math.PI*2);mmCtx.fill();
     //UI
     const g=gunDef[save.stashWeapons[currentGunIdx].id];
     document.getElementById("hp").innerText="生命:"+Math.max(0,player.hp);
     document.getElementById("armor").innerText="护甲:"+player.armor;
     document.getElementById("gunName").innerText="武器:"+g.name;
     document.getElementById("kill").innerText="击杀:"+player.kill;
     document.getElementById("extractText").innerText=extractActive?"撤离点已开启!前往绿色圆圈撤离":"等待撤离开启";
     document.getElementById("medText").innerText="药品:"+player.meds;
     if(currentMission){
         document.getElementById("missionText").innerText="任务:"+currentMission.desc+" ("+missionProgress+"/"+currentMission.target+")";
     }
     if(extractActive){
         const sec=Math.floor(extractCountdown/20);
         document.getElementById("timerText").innerText="撤离倒计时:"+sec+"秒";
     }else{
         document.getElementById("timerText").innerText="撤离倒计时:‑‑";
     }
     if(player.hp<=0){
         ctx.fillStyle="rgba(0,0,0,.65)";ctx.fillRect(0,0,W,H);
         ctx.fillStyle="#fff";ctx.font="36px sans‑serif";ctx.textAlign="center";
         ctx.fillText("任务失败!击杀:"+player.kill,W/2,H/2);
         setTimeout(()=>endGame(false),1800);
     }
 }
 loop();
 </script>
 </body>
 </html>

Game Source: 迷你三角洲‑V5

Creator: SuperFlare63

Libraries: none

Complexity: complex (521 lines, 24.9 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: v5-superflare63" to link back to the original. Then publish at arcadelab.ai/publish.