表情包吞噬
by QuantumPenguin79362 lines16.7 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;font-family:system-ui,sans-serif}
html,body{width:100vw;height:100vh;overflow:hidden;background:#4477dd;color:#fff}
#app{width:100%;height:100%;position:relative}
/* 竖屏强制显示旋转提示,横屏隐藏 */
#rotateWarn{
position:absolute;top:0;left:0;width:100%;height:100%;
background:#111;z-index:999;
display:flex;flex-direction:column;align-items:center;justify-content:center;
font-size:28px;text-align:center;padding:20px;
}
@media screen and (orientation:landscape){
#rotateWarn{display:none;}
}
#menuScreen{position:absolute;top:0;left:0;width:100%;height:100%;background:#4477dd;z-index:50;}
.menu-btn{
position:absolute;left:50px;width:170px;padding:14px 0;
background:#2c4eb8;border:none;border-radius:10px;color:#fff;
font-size:21px;text-align:center;cursor:pointer;
}
#bShop{top:130px}
#bSuite{top:200px}
#bStart{top:270px}
#bAch{top:340px}
#rolePreview{
position:absolute;right:180px;top:260px;
font-size:140px;pointer-events:none;
}
#ptsLabel{position:absolute;top:20px;right:20px;font-size:22px;color:#ffdd44}
#roundLabel{position:absolute;top:52px;right:20px;font-size:17px;color:#ffdd44}
#gameContainer{position:absolute;top:0;left:0;width:100%;height:100%;background:#222;display:none;z-index:40;}
#canvas{width:100%;height:100%;display:block;background:#fff;}
.tipText{position:absolute;top:12px;left:0;width:100%;text-align:center;font-size:22px;color:#000;pointer-events:none;z-index:21;}
#joystickBase{
position:absolute;left:20px;bottom:20px;width:120px;height:120px;
border-radius:50%;background:rgba(60,60,60,0.45);
border:2px solid rgba(255,255,255,0.55);touch-action:none;z-index:24;
}
#joystickKnob{
position:absolute;left:50%;top:50%;width:44px;height:44px;margin:-22px;
border-radius:50%;background:rgba(30,120,240,0.8);
border:2px solid rgba(255,255,255,0.7);pointer-events:none;
transform:translate(-50%,-50%);
}
.popEnd{
position:absolute;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.8);display:none;flex-direction:column;
align-items:center;justify-content:center;z-index:60;font-size:26px;padding:20px;
}
.popEnd button{
margin-top:20px;padding:10px 22px;font-size:18px;border:none;
border-radius:8px;background:#448aff;color:#fff;cursor:pointer;
}
.subPage{
position:absolute;top:0;left:0;width:100%;height:100%;
background:#4477dd;display:none;z-index:55;padding:20px;overflow-y:auto;
}
.backBtn{
position:absolute;top:10px;left:10px;padding:8px 16px;
background:#2c4eb8;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:16px;
}
.shopTabWrap{display:flex;justify-content:center;gap:10px;margin-bottom:20px}
.shopTab{padding:10px 20px;background:#2c4eb8;border:none;color:#fff;border-radius:6px;cursor:pointer}
.shopTab.active{background:#1c3a96}
.shopGrid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.itemBox{background:#2c4eb8;padding:10px;border-radius:8px;text-align:center}
.itemName{font-size:16px;margin-bottom:4px}
.itemPrice{color:#ffdd44;font-size:14px}
.buyBtn,.equipBtn{margin-top:6px;padding:4px 10px;border:none;color:#fff;border-radius:4px;cursor:pointer}
.buyBtn{background:#22aa44}
.equipBtn{background:#2196F3}
.achBox{background:#2c4eb8;padding:14px;margin:10px;border-radius:8px;text-align:center;font-size:17px}
.achDone{color:#7cfc00}
.achLock{color:#cccccc}
</style>
</head>
<body>
<div id="app">
<div id="rotateWarn">请把手机横过来<br>横着拿才能玩游戏</div>
<div id="menuScreen">
<button class="menu-btn" onclick="openShop()">商店</button>
<button class="menu-btn" onclick="openSuite()">套装</button>
<button class="menu-btn" onclick="startGame()">开始游戏</button>
<button class="menu-btn" onclick="openAchievement()">成就</button>
<div id="rolePreview">😀</div>
<div id="ptsLabel">积分:0</div>
<div id="roundLabel"></div>
</div>
<div class="subPage" id="shopPage">
<button class="backBtn" onclick="closeShop()">返回</button>
<div class="shopTabWrap">
<button class="shopTab active">皮肤</button>
<button class="shopTab">装饰</button>
</div>
<div class="shopGrid" id="shopContent"></div>
</div>
<div class="subPage" id="suitePage">
<button class="backBtn" onclick="closeSuite()">返回</button>
<div class="achBox"><div>圣诞节套装</div><div>10000积分</div><button class="buyBtn">购买并穿戴</button></div>
<div class="achBox"><div>武士套装</div><div>10000积分</div><button class="buyBtn">购买并穿戴</button></div>
<div class="achBox"><div>魔法套装</div><div>10000积分</div><button class="buyBtn">购买并穿戴</button></div>
</div>
<div class="subPage" id="achPage">
<button class="backBtn" onclick="closeAchievement()">返回</button>
<div class="achBox">
<div>🏅生存大师</div>
<div>单局完整坚持20分钟</div>
<div id="ach1Txt">未解锁|奖励:500积分</div>
</div>
<div class="achBox">
<div>💀厄运挑战者</div>
<div>连续失败100局,通关即重置</div>
<div id="ach2Txt">未解锁|奖励:10000积分</div>
</div>
<div class="achBox">
<div>🍪吞噬达人</div>
<div>连续对局累计吞噬100个表情包,失败即清零</div>
<div id="ach3Txt">未解锁|奖励:20000积分</div>
</div>
<div style="margin-top:14px;font-size:16px">
<div>连续失败局数:<span id="failStreakTxt">0</span></div>
<div>连续吞噬表情包数:<span id="eatStreakTxt">0</span></div>
</div>
</div>
<div id="gameContainer">
<canvas id="canvas"></canvas>
<div class="tipText" id="tipText"></div>
<div id="joystickBase"><div id="joystickKnob"></div></div>
<div class="popEnd" id="popWin"><div>🎉恭喜通关!升到最高等级Lv10!</div><div id="winPt"></div><button onclick="backToMenu()">返回主界面</button></div>
<div class="popEnd" id="popEdge"><div>💥跑出地图红线范围!游戏结束</div><div id="edgePt"></div><button onclick="backToMenu()">返回主界面</button></div>
<div class="popEnd" id="popTimeUp"><div>⏱20分钟时间到!</div><div id="timePt"></div><button onclick="backToMenu()">返回主界面</button></div>
<div class="popEnd" id="popDangerZone"><div>⚠️奖励区关闭时还在区内!出局</div><div id="dzPt"></div><button onclick="backToMenu()">返回主界面</button></div>
<div class="popEnd" id="popHitBad"><div>☠️碰到❌危险表情包!游戏失败</div><div id="badPt"></div><button onclick="backToMenu()">返回主界面</button></div>
</div>
</div>
<script>
const canvas=document.getElementById("canvas");
const ctx=canvas.getContext("2d");
const tipDom=document.getElementById("tipText");
const joyBase=document.getElementById("joystickBase");
const joyKnob=document.getElementById("joystickKnob");
const LVL_FACE=["😀","🙂","😊","😃","😄","😆","😎","🤩","😇","🥳"];
const MAP_MM=3000;
const PLAYER_SPEED=42;
const AI_SPEED=32;
const SAFE_R=220;
const GAME_DUR=1200;
const BONUS_OPEN_T=600;
const BONUS_CLOSE_T=780;
const FRUITS=["🍎","🍊","🍇","🍓","🍌"];
let PX_PER_MM=1;
let save=JSON.parse(localStorage.getItem("emoSave")||JSON.stringify({
pts:0,skin:0,ach:{survive:false,streakFail:false,streakEat:false},
failStreak:0,eatStreak:0
}));
function saveWrite(){localStorage.setItem("emoSave",JSON.stringify(save));refreshUI();}
function refreshUI(){
document.getElementById("ptsLabel").innerText="积分:"+save.pts;
document.getElementById("failStreakTxt").innerText=save.failStreak;
document.getElementById("eatStreakTxt").innerText=save.eatStreak;
document.getElementById("ach1Txt").className=save.ach.survive?"achDone":"achLock";
document.getElementById("ach1Txt").innerText=save.ach.survive?"✅已解锁|奖励:500积分":"未解锁|奖励:500积分";
document.getElementById("ach2Txt").className=save.ach.streakFail?"achDone":"achLock";
document.getElementById("ach2Txt").innerText=save.ach.streakFail?"✅已解锁|奖励:10000积分":"未解锁|奖励:10000积分";
document.getElementById("ach3Txt").className=save.ach.streakEat?"achDone":"achLock";
document.getElementById("ach3Txt").innerText=save.ach.streakEat?"✅已解锁|奖励:20000积分":"未解锁|奖励:20000积分";
document.getElementById("rolePreview").innerText=LVL_FACE[save.skin];
}
let gameRun=false;
let player,emolist,fruitlist,followers;
let timeLeft,tipEnd,bonusOpen,bonusClosed,roundPt,lastT;
let joyDown=false,jx=0,jy=0;
function resetJoystick(){jx=0;jy=0;joyKnob.style.transform="translate(-50%,-50%)";}
joyBase.addEventListener("touchstart",e=>{joyDown=true;joyMove(e.touches[0]);e.preventDefault()});
joyBase.addEventListener("touchmove",e=>{joyMove(e.touches[0]);e.preventDefault()});
joyBase.addEventListener("touchend",()=>{joyDown=false;resetJoystick()});
joyBase.addEventListener("touchcancel",()=>{joyDown=false;resetJoystick()});
function joyMove(t){
const r=60;
const rect=joyBase.getBoundingClientRect();
let dx=t.clientX-(rect.left+rect.width/2);
let dy=t.clientY-(rect.top+rect.height/2);
const d=Math.hypot(dx,dy);
if(d>r){dx=dx/d*r;dy=dy/d*r;}
jx=dx/r;jy=dy/r;
joyKnob.style.transform=`translate(calc(-50% + ${dx}px),calc(-50% + ${dy}px))`;
}
function showTip(s){tipDom.innerText=s;tipEnd=Date.now()+4500;}
function clearTip(){tipDom.innerText="";}
function resize(){
const gc=document.getElementById("gameContainer");
canvas.width=gc.clientWidth;canvas.height=gc.clientHeight;
PX_PER_MM=Math.min(canvas.width,canvas.height)/MAP_MM;
}
window.addEventListener("resize",resize);
function backToMenu(isWin=false){
gameRun=false;clearTip();resetJoystick();
document.querySelectorAll(".popEnd").forEach(p=>p.style.display="none");
document.getElementById("gameContainer").style.display="none";
document.getElementById("menuScreen").style.display="block";
document.getElementById("roundLabel").innerText=roundPt>0?"本局获得:"+roundPt+"积分":"本局无积分";
if(!isWin){save.failStreak++;save.eatStreak=0;}
else save.failStreak=0;
save.pts+=roundPt;saveWrite();
}
function startGame(){
document.getElementById("menuScreen").style.display="none";
document.getElementById("gameContainer").style.display="block";
resize();gameRun=true;roundPt=0;
timeLeft=GAME_DUR;bonusOpen=false;bonusClosed=false;
player={x:MAP_MM/2,y:MAP_MM/2,lv:0};
emolist=[];fruitlist=[];followers=[];
lastT=performance.now();
requestAnimationFrame(gameLoop);
}
function inPlayerView(x,y){
const camW=canvas.width/PX_PER_MM,camH=canvas.height/PX_PER_MM;
return Math.abs(x-player.x)<camW*0.42&&Math.abs(y-player.y)<camH*0.42;
}
function spawnEmote(){
let x,y,ok=false,tryN=0;
while(!ok&&tryN<220){
x=Math.random()*MAP_MM;y=Math.random()*MAP_MM;
const dc=Math.hypot(x-MAP_MM/2,y-MAP_MM/2);
if(dc<SAFE_R){tryN++;continue;}
if(inPlayerView(x,y)){tryN++;continue;}
ok=true;
}
if(!ok)return;
const badCross=Math.random()<0.11;
const lv=Math.floor(Math.random()*LVL_FACE.length);
emolist.push({x,y,lv,badCross,dir:{x:(Math.random()-0.5)*1.2,y:(Math.random()-0.5)*1.2}});
}
function spawnFruit(){
if(!bonusOpen||bonusClosed)return;
let x=MAP_MM+180+Math.random()*750;
let y=MAP_MM/2-380+Math.random()*760;
fruitlist.push({x,y,type:FRUITS[Math.floor(Math.random()*FRUITS.length)]});
}
function checkMerge(){
const same=followers.filter(f=>f.lv===player.lv);
if(same.length>=3){
let cnt=0;
for(let i=followers.length-1;i>=0&&cnt<3;i--){
if(followers[i].lv===player.lv){followers.splice(i,1);cnt++;}
}
player.lv++;
showTip("合成成功!升级到Lv"+(player.lv+1));
if(player.lv>=LVL_FACE.length){
document.getElementById("winPt").innerText="本局积分:"+roundPt;
document.getElementById("popWin").style.display="flex";
gameRun=false;backToMenu(true);
}
}
}
function gameLoop(t){
if(!gameRun)return;
const dt=Math.min((t-lastT)/1000,0.05);lastT=t;timeLeft-=dt;
if(timeLeft<=BONUS_OPEN_T&&!bonusOpen){bonusOpen=true;showTip("奖励区开启!✅可以收表情,❌千万别碰");}
if(timeLeft<=BONUS_CLOSE_T&&!bonusClosed){bonusClosed=true;showTip("奖励区关闭!请尽快离开");}
if(joyDown){
player.x+=jx*PLAYER_SPEED*dt;
player.y+=jy*PLAYER_SPEED*dt;
}
if(player.x<0||player.x>MAP_MM||player.y<0||player.y>MAP_MM){
document.getElementById("edgePt").innerText="本局积分:"+roundPt;
document.getElementById("popEdge").style.display="flex";
gameRun=false;return;
}
for(let e of emolist){
e.x+=e.dir.x*AI_SPEED*dt;e.y+=e.dir.y*AI_SPEED*dt;
if(e.x<0||e.x>MAP_MM)e.dir.x*=-1;
if(e.y<0||e.y>MAP_MM)e.dir.y*=-1;
}
for(let i=emolist.length-1;i>=0;i--){
const e=emolist[i];
const d=Math.hypot(e.x-player.x,e.y-player.y);
if(d<52){
if(e.badCross){
document.getElementById("badPt").innerText="本局积分:"+roundPt;
document.getElementById("popHitBad").style.display="flex";
gameRun=false;return;
}
save.eatStreak++;roundPt+=10;
emolist.splice(i,1);
followers.push({
x:player.x+(Math.random()-0.5)*140,
y:player.y+(Math.random()-0.5)*140,
lv:e.lv,
angle:Math.random()*Math.PI*2
});
checkMerge();
}
}
for(let f of followers){
f.angle+=0.6*dt;
const orbit=70+followers.indexOf(f)*22;
f.x=player.x+Math.cos(f.angle)*orbit;
f.y=player.y+Math.sin(f.angle)*orbit;
}
for(let i=fruitlist.length-1;i>=0;i--){
const f=fruitlist[i];
if(Math.hypot(f.x-player.x,f.y-player.y)<50){roundPt+=1;fruitlist.splice(i,1);}
}
if(emolist.length<14&&Math.random()<0.028)spawnEmote();
if(fruitlist.length<7&&Math.random()<0.016)spawnFruit();
if(timeLeft<=0){
document.getElementById("timePt").innerText="本局积分:"+roundPt;
document.getElementById("popTimeUp").style.display="flex";
gameRun=false;
if(!save.ach.survive){save.ach.survive=true;save.pts+=500;}
backToMenu(true);return;
}
if(bonusClosed&&player.x>MAP_MM){
document.getElementById("dzPt").innerText="本局积分:"+roundPt;
document.getElementById("popDangerZone").style.display="flex";
gameRun=false;return;
}
ctx.clearRect(0,0,canvas.width,canvas.height);
const cx=canvas.width/2-player.x*PX_PER_MM;
const cy=canvas.height/2-player.y*PX_PER_MM;
ctx.strokeStyle="red";ctx.lineWidth=3;
ctx.strokeRect(cx,cy,MAP_MM*PX_PER_MM,MAP_MM*PX_PER_MM);
ctx.fillStyle="#aa2222";
ctx.fillRect(cx+MAP_MM*PX_PER_MM,cy,90*PX_PER_MM,780*PX_PER_MM);
ctx.fillRect(cx+MAP_MM*PX_PER_MM+90*PX_PER_MM,cy+MAP_MM/2*PX_PER_MM-390*PX_PER_MM,760*PX_PER_MM,780*PX_PER_MM);
ctx.strokeStyle="#dddddd";ctx.lineWidth=1;
for(let i=0;i<=MAP_MM;i+=500){
ctx.beginPath();ctx.moveTo(cx+i*PX_PER_MM,cy);ctx.lineTo(cx+i*PX_PER_MM,cy+MAP_MM*PX_PER_MM);ctx.stroke();
ctx.beginPath();ctx.moveTo(cx,cy+i*PX_PER_MM);ctx.lineTo(cx+MAP_MM*PX_PER_MM,cy+i*PX_PER_MM);ctx.stroke();
}
ctx.font="24px sans‑serif";ctx.textAlign="center";ctx.textBaseline="middle";
for(let e of emolist){
ctx.fillText(e.badCross?"❌":LVL_FACE[e.lv],cx+e.x*PX_PER_MM,cy+e.y*PX_PER_MM);
}
for(let f of followers){
ctx.fillText(LVL_FACE[f.lv],cx+f.x*PX_PER_MM,cy+f.y*PX_PER_MM);
}
for(let f of fruitlist){
ctx.fillText(f.type,cx+f.x*PX_PER_MM,cy+f.y*PX_PER_MM);
}
ctx.font="34px sans‑serif";
ctx.fillText(LVL_FACE[player.lv],cx+player.x*PX_PER_MM,cy+player.y*PX_PER_MM);
if(Date.now()>tipEnd)clearTip();
requestAnimationFrame(gameLoop);
}
function openShop(){document.getElementById("menuScreen").style.display="none";document.getElementById("shopPage").style.display="block";}
function closeShop(){document.getElementById("shopPage").style.display="none";document.getElementById("menuScreen").style.display="block";}
function openSuite(){document.getElementById("menuScreen").style.display="none";document.getElementById("suitePage").style.display="block";}
function closeSuite(){document.getElementById("suitePage").style.display="none";document.getElementById("menuScreen").style.display="block";}
function openAchievement(){document.getElementById("menuScreen").style.display="none";document.getElementById("achPage").style.display="block";}
function closeAchievement(){document.getElementById("achPage").style.display="none";document.getElementById("menuScreen").style.display="block";}
refreshUI();
</script>
</body>
</html>Game Source: 表情包吞噬
Creator: QuantumPenguin79
Libraries: none
Complexity: complex (362 lines, 16.7 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-quantumpenguin79" to link back to the original. Then publish at arcadelab.ai/publish.