摸鱼反应堆
by CrystalPenguin76463 lines14.3 KB
<!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.0, user-scalable=no">
<title>摸鱼反应堆</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
body{background:#000;overflow:hidden;height:100vh;width:100vw;font-family:system-ui,sans-serif;}
canvas{display:block;width:100vw;height:100vh;}
.popup{
position:fixed;inset:0;background:rgba(0,0,0,0.85);
display:flex;align-items:center;justify-content:center;
z-index:99;
}
.box{
background:#222;color:#fff;padding:32px 24px;border-radius:16px;
width:min(88vw,360px);text-align:center;
}
.box h2{margin-bottom:16px;color:#0cf;}
.box p{line-height:1.6;margin:8px 0;color:#ccc;}
.btn{
margin-top:20px;padding:12px 24px;border:none;border-radius:8px;
background:#0cf;color:#000;font-size:16px;font-weight:bold;
width:100%;
}
.btn:active{opacity:0.7}
.hidden{display:none !important;}
.red-border{box-shadow:inset 0 0 0 8px #f33;animation:redFlash 0.3s infinite alternate;}
@keyframes redFlash{from{box-shadow:inset 0 0 0 6px #f33}to{box-shadow:inset 0 0 0 12px #f00}}
</style>
</head>
<body>
<canvas id="game"></canvas>
<!-- 开局引导弹窗 -->
<div id="guidePopup" class="popup">
<div class="box">
<h2>摸鱼反应堆</h2>
<p>👔老板脸正对中间反应堆时千万别点!会被抓扣分</p>
<p>老板脸转向左边/右边墙壁时,放心点击攒能量</p>
<p>⚠️紧急会议全屏变红,抓紧连点刷分</p>
<button class="btn" id="startBtn">点击开始游戏</button>
</div>
</div>
<!-- 结算弹窗 -->
<div id="endPopup" class="popup hidden">
<div class="box">
<h2>游戏结束!</h2>
<p>本次能量:<span id="finalEnergy">0</span></p>
<p>被抓包次数:<span id="catchCount">0</span></p>
<p>紧急会议触发:<span id="meetingCount">0</span></p>
<p>历史最高:<span id="highScore">0</span></p>
<button class="btn" id="restartBtn">再来一局</button>
<button class="btn" id="shareBtn" style="margin-top:10px;background:#444;color:#fff;">复制分享文案</button>
</div>
</div>
<script>
// 画布初始化
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
function resizeCanvas(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize',resizeCanvas);
// 音频工具
let audioCtx = null;
function initAudio(){if(!audioCtx)audioCtx = new (window.AudioContext || window.webkitAudioContext)();}
function playTone(freq,dur,type='sine'){
if(!audioCtx)return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);gain.connect(audioCtx.destination);
osc.type = type;osc.frequency.value = freq;
gain.gain.setValueAtTime(0.2,audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+dur);
osc.start();osc.stop(audioCtx.currentTime+dur);
}
const audio = {
success:()=>playTone(880,0.15,'sine'),
catch:()=>playTone(120,0.3,'triangle'),
meeting:()=>playTone(1200,0.06,'square')
}
// 游戏全局状态
const game = {
running:false,timeLeft:60,energy:0,maxEnergy:100,
catchTimes:0,meetingTimes:0,highScore:parseInt(localStorage.getItem('fishHigh'))||0,
reactor:{x:0,y:0,baseR:70,pulse:0,pulseSpeed:0.06,meetingShake:0},
boss:{x:0,y:60,w:80,speed:0.6,dir:1,//1右 -1左
lookDir:'front',//left/right/front
lookTransition:0.3,lookTimer:0
},
emergency:{active:false,duration:4000,timer:0,nextTimer:0,nextDelay:0},
floatTexts:[],
flashRed:0
};
// 弹窗DOM
const guidePopup = document.getElementById('guidePopup');
const endPopup = document.getElementById('endPopup');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const shareBtn = document.getElementById('shareBtn');
const finalEnergyEl = document.getElementById('finalEnergy');
const catchCountEl = document.getElementById('catchCount');
const meetingCountEl = document.getElementById('meetingCount');
const highScoreEl = document.getElementById('highScore');
// 重置游戏数据
function resetGame(){
game.running = true;
game.timeLeft = 60;
game.energy = 0;
game.catchTimes = 0;
game.meetingTimes = 0;
game.floatTexts = [];
game.flashRed = 0;
game.emergency.active = false;
game.reactor.x = canvas.width/2;
game.reactor.y = canvas.height/2;
game.reactor.pulse = 0;
game.boss.x = game.boss.w;
game.boss.dir = 1;
game.boss.lookDir = 'front';
game.boss.lookTimer = 0;
// 随机8-12秒触发第一次紧急会议
game.emergency.nextDelay = 8000 + Math.random()*4000;
game.emergency.nextTimer = 0;
}
// 开始游戏
startBtn.addEventListener('click',()=>{
initAudio();
guidePopup.classList.add('hidden');
resetGame();
requestAnimationFrame(gameLoop);
});
restartBtn.addEventListener('click',()=>{
endPopup.classList.add('hidden');
resetGame();
requestAnimationFrame(gameLoop);
});
shareBtn.addEventListener('click',async ()=>{
const text = `我在摸鱼反应堆里攒了${game.energy}点能量,你敢挑战吗?`;
await navigator.clipboard.writeText(text);
alert('分享文案已复制!');
});
// 【重写优化版老板绘制:朝向极其明显,带视线射线+头部旋转】
function drawBoss(){
const b = game.boss;
ctx.save();
ctx.translate(b.x,b.y);
const size = b.w;
// 根据朝向旋转头部,区分度拉满
let rotateAngle = 0;
let rayEndX = 0, rayEndY = -size * 4;
if(b.lookDir === "left"){
rotateAngle = -Math.PI / 6;
rayEndX = -canvas.width * 0.6;
}else if(b.lookDir === "right"){
rotateAngle = Math.PI / 6;
rayEndX = canvas.width * 0.6;
}else{
// front正视,视线直指反应堆中心
rayEndX = canvas.width/2 - b.x;
rayEndY = canvas.height/2 - b.y;
}
// 正视老板增加红色警告外发光
if(b.lookDir === "front"){
ctx.shadowColor = "#ff2222";
ctx.shadowBlur = 20;
}
// 身体西装
ctx.fillStyle='#223';
ctx.fillRect(-size/2,-size/2,size,size*0.8);
// 头部容器旋转
ctx.save();
ctx.rotate(rotateAngle);
// 脑袋底色
ctx.fillStyle='#ffe0cc';
ctx.beginPath();ctx.arc(0,-size*0.35,size*0.22,0,Math.PI*2);ctx.fill();
// 领带
ctx.fillStyle='#d22';
ctx.beginPath();
ctx.moveTo(0,-size*0.1);
ctx.lineTo(-size*0.08,size*0.3);
ctx.lineTo(size*0.08,size*0.3);
ctx.closePath();ctx.fill();
// 眼睛大幅偏移,一眼分清朝向
ctx.fillStyle='#000';
const eyeY = -size*0.38;
const eyeBaseOffset = size*0.08;
if(b.lookDir === 'left'){
// 看向左边,眼球全部左移
ctx.beginPath();
ctx.arc(-eyeBaseOffset - 8, eyeY, 4, 0, Math.PI*2);
ctx.arc(eyeBaseOffset - 12, eyeY, 4, 0, Math.PI*2);
ctx.fill();
}else if(b.lookDir === 'right'){
// 看向右边,眼球全部右移
ctx.beginPath();
ctx.arc(-eyeBaseOffset + 12, eyeY, 4, 0, Math.PI*2);
ctx.arc(eyeBaseOffset + 8, eyeY, 4, 0, Math.PI*2);
ctx.fill();
}else{
// front正视,双眼居中对准玩家
ctx.beginPath();
ctx.arc(-eyeBaseOffset, eyeY, 4, 0, Math.PI*2);
ctx.arc(eyeBaseOffset, eyeY, 4, 0, Math.PI*2);
ctx.fill();
}
ctx.restore();
// 绘制视线指示射线(最关键!直观看到看哪里)
ctx.strokeStyle = "rgba(0,0,0,0.7)";
ctx.lineWidth = 3;
ctx.setLineDash([8,6]);
ctx.beginPath();
ctx.moveTo(0, -size*0.35);
ctx.lineTo(rayEndX, rayEndY);
ctx.stroke();
ctx.setLineDash([]);
ctx.shadowBlur = 0;
ctx.restore();
}
// 绘制反应堆
function drawReactor(){
const r = game.reactor;
let pulseScale = 1 + Math.sin(r.pulse)*0.12;
if(game.emergency.active){
pulseScale = 1 + Math.sin(r.pulse*3)*0.25;
}
const radius = r.baseR * pulseScale;
const shakeX = game.emergency.active ? (Math.random()-0.5)*r.meetingShake : 0;
const shakeY = game.emergency.active ? (Math.random()-0.5)*r.meetingShake : 0;
const cx = r.x + shakeX;
const cy = r.y + shakeY;
// 径向渐变
const grad = ctx.createRadialGradient(cx,cy,0,cx,cy,radius);
if(game.emergency.active){
grad.addColorStop(0,'#ffdd00');
grad.addColorStop(1,'#ff4400');
}else{
grad.addColorStop(0,'#00f0ff');
grad.addColorStop(1,'#002266');
}
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.fillStyle = grad;
ctx.fill();
// 发光外描边
ctx.shadowColor = game.emergency.active ? '#ff7700' : '#0cf';
ctx.shadowBlur = 30;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();
ctx.shadowBlur = 0;
// 能量文字
let energyColor;
if(game.energy < 30) energyColor = '#f33';
else if(game.energy < 70) energyColor = '#fc0';
else energyColor = '#3f3';
ctx.fillStyle = energyColor;
ctx.font = 'bold 32px system-ui';
ctx.textAlign = 'center';
ctx.fillText(game.energy + '/' + game.maxEnergy, cx, cy - radius - 16);
}
// 绘制浮动文字
function drawFloatText(){
game.floatTexts.forEach((t,i)=>{
ctx.fillStyle = t.color;
ctx.font = 'bold 28px system-ui';
ctx.textAlign = 'center';
ctx.globalAlpha = t.alpha;
ctx.fillText(t.text,t.x,t.y);
ctx.globalAlpha = 1;
t.y -= 1.2;
t.alpha -= 0.018;
if(t.alpha <= 0) game.floatTexts.splice(i,1);
})
}
// 绘制UI顶部文字
function drawUI(){
// 剩余时间 左上
ctx.fillStyle='#fff';
ctx.font='bold 22px system-ui';
ctx.textAlign='left';
ctx.fillText(`剩余:${Math.ceil(game.timeLeft)}s`,20,36);
// 当前能量 右上
ctx.textAlign='right';
ctx.fillText(`能量:${game.energy}/${game.maxEnergy}`,canvas.width-20,36);
}
// 绘制背景
function drawBg(){
ctx.fillStyle='#1a1a1a';
ctx.fillRect(0,0,canvas.width,canvas.height);
// 磨砂噪点简化
ctx.fillStyle='rgba(255,255,255,0.02)';
for(let i=0;i<120;i++){
const x = Math.random()*canvas.width;
const y = Math.random()*canvas.height;
ctx.fillRect(x,y,2,2);
}
// 红色警告闪烁遮罩
if(game.flashRed>0){
ctx.fillStyle=`rgba(255,40,40,${game.flashRed*0.35})`;
ctx.fillRect(0,0,canvas.width,canvas.height);
game.flashRed -= 0.04;
}
// 紧急会议红色边框
if(game.emergency.active){
ctx.strokeStyle='#f22';
ctx.lineWidth=10;
ctx.strokeRect(4,4,canvas.width-8,canvas.height-8);
}
}
// 点击处理(判定逻辑完全不变,仅视觉升级)
function handleTap(touchX,touchY){
if(!game.running)return;
const r = game.reactor;
const dist = Math.hypot(touchX - r.x, touchY - r.y);
const hitRadius = r.baseR + 50;
if(dist > hitRadius) return; // 没点到反应堆区域
if(game.emergency.active){
// 紧急会议加分
game.energy = Math.min(game.energy+5,game.maxEnergy);
game.floatTexts.push({
text:'+5',x:r.x,y:r.y-40,color:'#ff0',alpha:1
});
audio.meeting();
return;
}
const bossLook = game.boss.lookDir;
// 判定核心:只有front才是看向你
if(bossLook === 'front'){
// 被老板抓包
game.energy = Math.max(game.energy-2,0);
game.catchTimes++;
game.flashRed = 1;
game.floatTexts.push({
text:'-2',x:r.x,y:r.y-40,color:'#f33',alpha:1
});
audio.catch();
}else{
// 左/右朝向墙壁,摸鱼成功
game.energy = Math.min(game.energy+2,game.maxEnergy);
game.floatTexts.push({
text:'+2',x:r.x,y:r.y-40,color:'#3f3',alpha:1
});
audio.success();
}
}
// 触摸绑定
canvas.addEventListener('touchstart',e=>{
e.preventDefault();
const t = e.touches[0];
handleTap(t.clientX,t.clientY);
})
canvas.addEventListener('mousedown',e=>handleTap(e.clientX,e.clientY));
// 游戏主循环
let lastTime = 0;
function gameLoop(ts){
const dt = (ts - lastTime)/1000;
lastTime = ts;
if(!game.running)return;
// 倒计时
game.timeLeft -= dt;
if(game.timeLeft <= 0){
game.running = false;
// 更新最高分
if(game.energy > game.highScore){
game.highScore = game.energy;
localStorage.setItem('fishHigh',game.highScore);
}
// 结算面板赋值
finalEnergyEl.textContent = game.energy;
catchCountEl.textContent = game.catchTimes;
meetingCountEl.textContent = game.meetingTimes;
highScoreEl.textContent = game.highScore;
endPopup.classList.remove('hidden');
return;
}
// 反应堆脉动
game.reactor.pulse += game.reactor.pulseSpeed;
// 老板移动
const b = game.boss;
b.x += b.speed * b.dir;
const boundRight = canvas.width - b.w/2;
const boundLeft = b.w/2;
// 碰到边界转向
if(b.x >= boundRight){
b.dir = -1;
b.lookDir = 'right';
}else if(b.x <= boundLeft){
b.dir = 1;
b.lookDir = 'left';
}else{
// 随机切换正面看向反应堆
b.lookTimer += dt;
if(b.lookTimer > 2.2 + Math.random()*1.8){
b.lookDir = 'front';
setTimeout(()=>{
if(b.dir>0)b.lookDir='left';
else b.lookDir='right';
},b.lookTransition*1000);
b.lookTimer = 0;
}
}
// 紧急会议计时器
game.emergency.nextTimer += dt*1000;
if(!game.emergency.active && game.emergency.nextTimer >= game.emergency.nextDelay){
game.emergency.active = true;
game.emergency.timer = game.emergency.duration;
game.meetingTimes++;
}
if(game.emergency.active){
game.emergency.timer -= dt*1000;
if(game.emergency.timer <= 0){
game.emergency.active = false;
game.emergency.nextTimer = 0;
game.emergency.nextDelay = 8000 + Math.random()*4000;
}
}
// 渲染层
drawBg();
drawReactor();
if(!game.emergency.active) drawBoss();
drawFloatText();
drawUI();
requestAnimationFrame(gameLoop);
}
</script>
</body>
</html>Game Source: 摸鱼反应堆
Creator: CrystalPenguin76
Libraries: none
Complexity: complex (463 lines, 14.3 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: game-crystalpenguin76-mr1hqhgh" to link back to the original. Then publish at arcadelab.ai/publish.